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 #include "messagemodel.h"
30
31 #include <QtCore/QCoreApplication>
32 #include <QtCore/QDebug>
33
34 #include <QtWidgets/QMessageBox>
35 #include <QtGui/QPainter>
36 #include <QtGui/QPixmap>
37 #include <QtGui/QTextDocument>
38
39 #include <private/qtranslator_p.h>
40
41 #include <limits.h>
42
43 QT_BEGIN_NAMESPACE
44
45 /******************************************************************************
46 *
47 * MessageItem
48 *
49 *****************************************************************************/
50
MessageItem(const TranslatorMessage & message)51 MessageItem::MessageItem(const TranslatorMessage &message)
52 : m_message(message),
53 m_danger(false)
54 {
55 if (m_message.translation().isEmpty())
56 m_message.setTranslation(QString());
57 }
58
59
compare(const QString & findText,bool matchSubstring,Qt::CaseSensitivity cs) const60 bool MessageItem::compare(const QString &findText, bool matchSubstring,
61 Qt::CaseSensitivity cs) const
62 {
63 return matchSubstring
64 ? text().indexOf(findText, 0, cs) >= 0
65 : text().compare(findText, cs) == 0;
66 }
67
68 /******************************************************************************
69 *
70 * ContextItem
71 *
72 *****************************************************************************/
73
ContextItem(const QString & context)74 ContextItem::ContextItem(const QString &context)
75 : m_context(context),
76 m_finishedCount(0),
77 m_finishedDangerCount(0),
78 m_unfinishedDangerCount(0),
79 m_nonobsoleteCount(0)
80 {}
81
appendToComment(const QString & str)82 void ContextItem::appendToComment(const QString &str)
83 {
84 if (!m_comment.isEmpty())
85 m_comment += QLatin1String("\n\n");
86 m_comment += str;
87 }
88
messageItem(int i) const89 MessageItem *ContextItem::messageItem(int i) const
90 {
91 if (i >= 0 && i < msgItemList.count())
92 return const_cast<MessageItem *>(&msgItemList[i]);
93 Q_ASSERT(i >= 0 && i < msgItemList.count());
94 return 0;
95 }
96
findMessage(const QString & sourcetext,const QString & comment) const97 MessageItem *ContextItem::findMessage(const QString &sourcetext, const QString &comment) const
98 {
99 for (int i = 0; i < messageCount(); ++i) {
100 MessageItem *mi = messageItem(i);
101 if (mi->text() == sourcetext && mi->comment() == comment)
102 return mi;
103 }
104 return 0;
105 }
106
107 /******************************************************************************
108 *
109 * DataModel
110 *
111 *****************************************************************************/
112
DataModel(QObject * parent)113 DataModel::DataModel(QObject *parent)
114 : QObject(parent),
115 m_modified(false),
116 m_numMessages(0),
117 m_srcWords(0),
118 m_srcChars(0),
119 m_srcCharsSpc(0),
120 m_language(QLocale::Language(-1)),
121 m_sourceLanguage(QLocale::Language(-1)),
122 m_country(QLocale::Country(-1)),
123 m_sourceCountry(QLocale::Country(-1))
124 {}
125
normalizedTranslations(const MessageItem & m) const126 QStringList DataModel::normalizedTranslations(const MessageItem &m) const
127 {
128 return Translator::normalizedTranslations(m.message(), m_numerusForms.count());
129 }
130
contextItem(int context) const131 ContextItem *DataModel::contextItem(int context) const
132 {
133 if (context >= 0 && context < m_contextList.count())
134 return const_cast<ContextItem *>(&m_contextList[context]);
135 Q_ASSERT(context >= 0 && context < m_contextList.count());
136 return 0;
137 }
138
messageItem(const DataIndex & index) const139 MessageItem *DataModel::messageItem(const DataIndex &index) const
140 {
141 if (ContextItem *c = contextItem(index.context()))
142 return c->messageItem(index.message());
143 return 0;
144 }
145
findContext(const QString & context) const146 ContextItem *DataModel::findContext(const QString &context) const
147 {
148 for (int c = 0; c < m_contextList.count(); ++c) {
149 ContextItem *ctx = contextItem(c);
150 if (ctx->context() == context)
151 return ctx;
152 }
153 return 0;
154 }
155
findMessage(const QString & context,const QString & sourcetext,const QString & comment) const156 MessageItem *DataModel::findMessage(const QString &context,
157 const QString &sourcetext, const QString &comment) const
158 {
159 if (ContextItem *ctx = findContext(context))
160 return ctx->findMessage(sourcetext, comment);
161 return 0;
162 }
163
calcMergeScore(const DataModel * one,const DataModel * two)164 static int calcMergeScore(const DataModel *one, const DataModel *two)
165 {
166 int inBoth = 0;
167 for (int i = 0; i < two->contextCount(); ++i) {
168 ContextItem *oc = two->contextItem(i);
169 if (ContextItem *c = one->findContext(oc->context())) {
170 for (int j = 0; j < oc->messageCount(); ++j) {
171 MessageItem *m = oc->messageItem(j);
172 if (c->findMessage(m->text(), m->comment()))
173 ++inBoth;
174 }
175 }
176 }
177 return inBoth * 100 / two->messageCount();
178 }
179
isWellMergeable(const DataModel * other) const180 bool DataModel::isWellMergeable(const DataModel *other) const
181 {
182 if (!other->messageCount() || !messageCount())
183 return true;
184
185 return calcMergeScore(this, other) + calcMergeScore(other, this) > 90;
186 }
187
load(const QString & fileName,bool * langGuessed,QWidget * parent)188 bool DataModel::load(const QString &fileName, bool *langGuessed, QWidget *parent)
189 {
190 Translator tor;
191 ConversionData cd;
192 bool ok = tor.load(fileName, cd, QLatin1String("auto"));
193 if (!ok) {
194 QMessageBox::warning(parent, QObject::tr("Qt Linguist"), cd.error());
195 return false;
196 }
197
198 if (!tor.messageCount()) {
199 QMessageBox::warning(parent, QObject::tr("Qt Linguist"),
200 tr("The translation file '%1' will not be loaded because it is empty.")
201 .arg(fileName.toHtmlEscaped()));
202 return false;
203 }
204
205 Translator::Duplicates dupes = tor.resolveDuplicates();
206 if (!dupes.byId.isEmpty() || !dupes.byContents.isEmpty()) {
207 QString err = tr("<qt>Duplicate messages found in '%1':").arg(fileName.toHtmlEscaped());
208 int numdups = 0;
209 foreach (int i, dupes.byId) {
210 if (++numdups >= 5) {
211 err += tr("<p>[more duplicates omitted]");
212 goto doWarn;
213 }
214 err += tr("<p>* ID: %1").arg(tor.message(i).id().toHtmlEscaped());
215 }
216 foreach (int j, dupes.byContents) {
217 const TranslatorMessage &msg = tor.message(j);
218 if (++numdups >= 5) {
219 err += tr("<p>[more duplicates omitted]");
220 break;
221 }
222 err += tr("<p>* Context: %1<br>* Source: %2")
223 .arg(msg.context().toHtmlEscaped(), msg.sourceText().toHtmlEscaped());
224 if (!msg.comment().isEmpty())
225 err += tr("<br>* Comment: %3").arg(msg.comment().toHtmlEscaped());
226 }
227 doWarn:
228 QMessageBox::warning(parent, QObject::tr("Qt Linguist"), err);
229 }
230
231 m_srcFileName = fileName;
232 m_relativeLocations = (tor.locationsType() == Translator::RelativeLocations);
233 m_extra = tor.extras();
234 m_contextList.clear();
235 m_numMessages = 0;
236
237 QHash<QString, int> contexts;
238
239 m_srcWords = 0;
240 m_srcChars = 0;
241 m_srcCharsSpc = 0;
242
243 foreach (const TranslatorMessage &msg, tor.messages()) {
244 if (!contexts.contains(msg.context())) {
245 contexts.insert(msg.context(), m_contextList.size());
246 m_contextList.append(ContextItem(msg.context()));
247 }
248
249 ContextItem *c = contextItem(contexts.value(msg.context()));
250 if (msg.sourceText() == QLatin1String(ContextComment)) {
251 c->appendToComment(msg.comment());
252 } else {
253 MessageItem tmp(msg);
254 if (msg.type() == TranslatorMessage::Finished)
255 c->incrementFinishedCount();
256 if (msg.type() == TranslatorMessage::Finished || msg.type() == TranslatorMessage::Unfinished) {
257 doCharCounting(tmp.text(), m_srcWords, m_srcChars, m_srcCharsSpc);
258 doCharCounting(tmp.pluralText(), m_srcWords, m_srcChars, m_srcCharsSpc);
259 c->incrementNonobsoleteCount();
260 }
261 c->appendMessage(tmp);
262 ++m_numMessages;
263 }
264 }
265
266 // Try to detect the correct language in the following order
267 // 1. Look for the language attribute in the ts
268 // if that fails
269 // 2. Guestimate the language from the filename
270 // (expecting the qt_{en,de}.ts convention)
271 // if that fails
272 // 3. Retrieve the locale from the system.
273 *langGuessed = false;
274 QString lang = tor.languageCode();
275 if (lang.isEmpty()) {
276 lang = QFileInfo(fileName).baseName();
277 int pos = lang.indexOf(QLatin1Char('_'));
278 if (pos != -1)
279 lang.remove(0, pos + 1);
280 else
281 lang.clear();
282 *langGuessed = true;
283 }
284 QLocale::Language l;
285 QLocale::Country c;
286 Translator::languageAndCountry(lang, &l, &c);
287 if (l == QLocale::C) {
288 QLocale sys;
289 l = sys.language();
290 c = sys.country();
291 *langGuessed = true;
292 }
293 if (!setLanguageAndCountry(l, c))
294 QMessageBox::warning(parent, QObject::tr("Qt Linguist"),
295 tr("Linguist does not know the plural rules for '%1'.\n"
296 "Will assume a single universal form.")
297 .arg(m_localizedLanguage));
298 // Try to detect the correct source language in the following order
299 // 1. Look for the language attribute in the ts
300 // if that fails
301 // 2. Assume English
302 lang = tor.sourceLanguageCode();
303 if (lang.isEmpty()) {
304 l = QLocale::C;
305 c = QLocale::AnyCountry;
306 } else {
307 Translator::languageAndCountry(lang, &l, &c);
308 }
309 setSourceLanguageAndCountry(l, c);
310
311 setModified(false);
312
313 return true;
314 }
315
save(const QString & fileName,QWidget * parent)316 bool DataModel::save(const QString &fileName, QWidget *parent)
317 {
318 Translator tor;
319 for (DataModelIterator it(this); it.isValid(); ++it)
320 tor.append(it.current()->message());
321
322 tor.setLanguageCode(Translator::makeLanguageCode(m_language, m_country));
323 tor.setSourceLanguageCode(Translator::makeLanguageCode(m_sourceLanguage, m_sourceCountry));
324 tor.setLocationsType(m_relativeLocations ? Translator::RelativeLocations
325 : Translator::AbsoluteLocations);
326 tor.setExtras(m_extra);
327 ConversionData cd;
328 tor.normalizeTranslations(cd);
329 bool ok = tor.save(fileName, cd, QLatin1String("auto"));
330 if (ok)
331 setModified(false);
332 if (!cd.error().isEmpty())
333 QMessageBox::warning(parent, QObject::tr("Qt Linguist"), cd.error());
334 return ok;
335 }
336
saveAs(const QString & newFileName,QWidget * parent)337 bool DataModel::saveAs(const QString &newFileName, QWidget *parent)
338 {
339 if (!save(newFileName, parent))
340 return false;
341 m_srcFileName = newFileName;
342 return true;
343 }
344
release(const QString & fileName,bool verbose,bool ignoreUnfinished,TranslatorSaveMode mode,QWidget * parent)345 bool DataModel::release(const QString &fileName, bool verbose, bool ignoreUnfinished,
346 TranslatorSaveMode mode, QWidget *parent)
347 {
348 QFile file(fileName);
349 if (!file.open(QIODevice::WriteOnly)) {
350 QMessageBox::warning(parent, QObject::tr("Qt Linguist"),
351 tr("Cannot create '%2': %1").arg(file.errorString()).arg(fileName));
352 return false;
353 }
354 Translator tor;
355 QLocale locale(m_language, m_country);
356 tor.setLanguageCode(locale.name());
357 for (DataModelIterator it(this); it.isValid(); ++it)
358 tor.append(it.current()->message());
359 ConversionData cd;
360 cd.m_verbose = verbose;
361 cd.m_ignoreUnfinished = ignoreUnfinished;
362 cd.m_saveMode = mode;
363 bool ok = saveQM(tor, file, cd);
364 if (!ok)
365 QMessageBox::warning(parent, QObject::tr("Qt Linguist"), cd.error());
366 return ok;
367 }
368
doCharCounting(const QString & text,int & trW,int & trC,int & trCS)369 void DataModel::doCharCounting(const QString &text, int &trW, int &trC, int &trCS)
370 {
371 trCS += text.length();
372 bool inWord = false;
373 for (int i = 0; i < text.length(); ++i) {
374 if (text[i].isLetterOrNumber() || text[i] == QLatin1Char('_')) {
375 if (!inWord) {
376 ++trW;
377 inWord = true;
378 }
379 } else {
380 inWord = false;
381 }
382 if (!text[i].isSpace())
383 trC++;
384 }
385 }
386
setLanguageAndCountry(QLocale::Language lang,QLocale::Country country)387 bool DataModel::setLanguageAndCountry(QLocale::Language lang, QLocale::Country country)
388 {
389 if (m_language == lang && m_country == country)
390 return true;
391 m_language = lang;
392 m_country = country;
393
394 if (lang == QLocale::C || uint(lang) > uint(QLocale::LastLanguage)) // XXX does this make any sense?
395 lang = QLocale::English;
396 QByteArray rules;
397 bool ok = getNumerusInfo(lang, country, &rules, &m_numerusForms, 0);
398 QLocale loc(lang, country);
399 m_localizedLanguage = QLocale::countriesForLanguage(lang).size() > 1
400 //: <language> (<country>)
401 ? tr("%1 (%2)").arg(loc.nativeLanguageName(), loc.nativeCountryName())
402 : loc.nativeLanguageName();
403 m_countRefNeeds.clear();
404 for (int i = 0; i < rules.size(); ++i) {
405 m_countRefNeeds.append(!(rules.at(i) == Q_EQ && (i == (rules.size() - 2) || rules.at(i + 2) == (char)Q_NEWRULE)));
406 while (++i < rules.size() && rules.at(i) != (char)Q_NEWRULE) {}
407 }
408 m_countRefNeeds.append(true);
409 if (!ok) {
410 m_numerusForms.clear();
411 m_numerusForms << tr("Universal Form");
412 }
413 emit languageChanged();
414 setModified(true);
415 return ok;
416 }
417
setSourceLanguageAndCountry(QLocale::Language lang,QLocale::Country country)418 void DataModel::setSourceLanguageAndCountry(QLocale::Language lang, QLocale::Country country)
419 {
420 if (m_sourceLanguage == lang && m_sourceCountry == country)
421 return;
422 m_sourceLanguage = lang;
423 m_sourceCountry = country;
424 setModified(true);
425 }
426
updateStatistics()427 void DataModel::updateStatistics()
428 {
429 int trW = 0;
430 int trC = 0;
431 int trCS = 0;
432
433 for (DataModelIterator it(this); it.isValid(); ++it) {
434 const MessageItem *mi = it.current();
435 if (mi->isFinished()) {
436 const QStringList translations = mi->translations();
437 for (const QString &trnsl : translations)
438 doCharCounting(trnsl, trW, trC, trCS);
439 }
440 }
441
442 emit statsChanged(m_srcWords, m_srcChars, m_srcCharsSpc, trW, trC, trCS);
443 }
444
setModified(bool isModified)445 void DataModel::setModified(bool isModified)
446 {
447 if (m_modified == isModified)
448 return;
449 m_modified = isModified;
450 emit modifiedChanged();
451 }
452
prettifyPlainFileName(const QString & fn)453 QString DataModel::prettifyPlainFileName(const QString &fn)
454 {
455 static QString workdir = QDir::currentPath() + QLatin1Char('/');
456
457 return QDir::toNativeSeparators(fn.startsWith(workdir) ? fn.mid(workdir.length()) : fn);
458 }
459
prettifyFileName(const QString & fn)460 QString DataModel::prettifyFileName(const QString &fn)
461 {
462 if (fn.startsWith(QLatin1Char('=')))
463 return QLatin1Char('=') + prettifyPlainFileName(fn.mid(1));
464 else
465 return prettifyPlainFileName(fn);
466 }
467
468 /******************************************************************************
469 *
470 * DataModelIterator
471 *
472 *****************************************************************************/
473
DataModelIterator(DataModel * model,int context,int message)474 DataModelIterator::DataModelIterator(DataModel *model, int context, int message)
475 : DataIndex(context, message), m_model(model)
476 {
477 }
478
isValid() const479 bool DataModelIterator::isValid() const
480 {
481 return m_context < m_model->m_contextList.count();
482 }
483
operator ++()484 void DataModelIterator::operator++()
485 {
486 ++m_message;
487 if (m_message >= m_model->m_contextList.at(m_context).messageCount()) {
488 ++m_context;
489 m_message = 0;
490 }
491 }
492
current() const493 MessageItem *DataModelIterator::current() const
494 {
495 return m_model->messageItem(*this);
496 }
497
498
499 /******************************************************************************
500 *
501 * MultiMessageItem
502 *
503 *****************************************************************************/
504
MultiMessageItem(const MessageItem * m)505 MultiMessageItem::MultiMessageItem(const MessageItem *m)
506 : m_id(m->id()),
507 m_text(m->text()),
508 m_pluralText(m->pluralText()),
509 m_comment(m->comment()),
510 m_nonnullCount(0),
511 m_nonobsoleteCount(0),
512 m_editableCount(0),
513 m_unfinishedCount(0)
514 {
515 }
516
517 /******************************************************************************
518 *
519 * MultiContextItem
520 *
521 *****************************************************************************/
522
MultiContextItem(int oldCount,ContextItem * ctx,bool writable)523 MultiContextItem::MultiContextItem(int oldCount, ContextItem *ctx, bool writable)
524 : m_context(ctx->context()),
525 m_comment(ctx->comment()),
526 m_finishedCount(0),
527 m_editableCount(0),
528 m_nonobsoleteCount(0)
529 {
530 QList<MessageItem *> mList;
531 QList<MessageItem *> eList;
532 for (int j = 0; j < ctx->messageCount(); ++j) {
533 MessageItem *m = ctx->messageItem(j);
534 mList.append(m);
535 eList.append(0);
536 m_multiMessageList.append(MultiMessageItem(m));
537 }
538 for (int i = 0; i < oldCount; ++i) {
539 m_messageLists.append(eList);
540 m_writableMessageLists.append(0);
541 m_contextList.append(0);
542 }
543 m_messageLists.append(mList);
544 m_writableMessageLists.append(writable ? &m_messageLists.last() : 0);
545 m_contextList.append(ctx);
546 }
547
appendEmptyModel()548 void MultiContextItem::appendEmptyModel()
549 {
550 QList<MessageItem *> eList;
551 for (int j = 0; j < messageCount(); ++j)
552 eList.append(0);
553 m_messageLists.append(eList);
554 m_writableMessageLists.append(0);
555 m_contextList.append(0);
556 }
557
assignLastModel(ContextItem * ctx,bool writable)558 void MultiContextItem::assignLastModel(ContextItem *ctx, bool writable)
559 {
560 if (writable)
561 m_writableMessageLists.last() = &m_messageLists.last();
562 m_contextList.last() = ctx;
563 }
564
565 // XXX this is not needed, yet
moveModel(int oldPos,int newPos)566 void MultiContextItem::moveModel(int oldPos, int newPos)
567 {
568 m_contextList.insert(newPos, m_contextList[oldPos]);
569 m_messageLists.insert(newPos, m_messageLists[oldPos]);
570 m_writableMessageLists.insert(newPos, m_writableMessageLists[oldPos]);
571 removeModel(oldPos < newPos ? oldPos : oldPos + 1);
572 }
573
removeModel(int pos)574 void MultiContextItem::removeModel(int pos)
575 {
576 m_contextList.removeAt(pos);
577 m_messageLists.removeAt(pos);
578 m_writableMessageLists.removeAt(pos);
579 }
580
putMessageItem(int pos,MessageItem * m)581 void MultiContextItem::putMessageItem(int pos, MessageItem *m)
582 {
583 m_messageLists.last()[pos] = m;
584 }
585
appendMessageItems(const QList<MessageItem * > & m)586 void MultiContextItem::appendMessageItems(const QList<MessageItem *> &m)
587 {
588 QList<MessageItem *> nullItems = m; // Basically, just a reservation
589 for (int i = 0; i < nullItems.count(); ++i)
590 nullItems[i] = 0;
591 for (int i = 0; i < m_messageLists.count() - 1; ++i)
592 m_messageLists[i] += nullItems;
593 m_messageLists.last() += m;
594 foreach (MessageItem *mi, m)
595 m_multiMessageList.append(MultiMessageItem(mi));
596 }
597
removeMultiMessageItem(int pos)598 void MultiContextItem::removeMultiMessageItem(int pos)
599 {
600 for (int i = 0; i < m_messageLists.count(); ++i)
601 m_messageLists[i].removeAt(pos);
602 m_multiMessageList.removeAt(pos);
603 }
604
firstNonobsoleteMessageIndex(int msgIdx) const605 int MultiContextItem::firstNonobsoleteMessageIndex(int msgIdx) const
606 {
607 for (int i = 0; i < m_messageLists.size(); ++i)
608 if (m_messageLists[i][msgIdx] && !m_messageLists[i][msgIdx]->isObsolete())
609 return i;
610 return -1;
611 }
612
findMessage(const QString & sourcetext,const QString & comment) const613 int MultiContextItem::findMessage(const QString &sourcetext, const QString &comment) const
614 {
615 for (int i = 0, cnt = messageCount(); i < cnt; ++i) {
616 MultiMessageItem *m = multiMessageItem(i);
617 if (m->text() == sourcetext && m->comment() == comment)
618 return i;
619 }
620 return -1;
621 }
622
findMessageById(const QString & id) const623 int MultiContextItem::findMessageById(const QString &id) const
624 {
625 for (int i = 0, cnt = messageCount(); i < cnt; ++i) {
626 MultiMessageItem *m = multiMessageItem(i);
627 if (m->id() == id)
628 return i;
629 }
630 return -1;
631 }
632
633 /******************************************************************************
634 *
635 * MultiDataModel
636 *
637 *****************************************************************************/
638
639 static const uchar paletteRGBs[7][3] = {
640 { 236, 244, 255 }, // blue
641 { 236, 255, 255 }, // cyan
642 { 236, 255, 232 }, // green
643 { 255, 255, 230 }, // yellow
644 { 255, 242, 222 }, // orange
645 { 255, 236, 236 }, // red
646 { 252, 236, 255 } // purple
647 };
648
MultiDataModel(QObject * parent)649 MultiDataModel::MultiDataModel(QObject *parent) :
650 QObject(parent),
651 m_numFinished(0),
652 m_numEditable(0),
653 m_numMessages(0),
654 m_modified(false)
655 {
656 for (int i = 0; i < 7; ++i)
657 m_colors[i] = QColor(paletteRGBs[i][0], paletteRGBs[i][1], paletteRGBs[i][2]);
658
659 m_bitmap = QBitmap(8, 8);
660 m_bitmap.clear();
661 QPainter p(&m_bitmap);
662 for (int j = 0; j < 8; ++j)
663 for (int k = 0; k < 8; ++k)
664 if ((j + k) & 4)
665 p.drawPoint(j, k);
666 }
667
~MultiDataModel()668 MultiDataModel::~MultiDataModel()
669 {
670 qDeleteAll(m_dataModels);
671 }
672
brushForModel(int model) const673 QBrush MultiDataModel::brushForModel(int model) const
674 {
675 QBrush brush(m_colors[model % 7]);
676 if (!isModelWritable(model))
677 brush.setTexture(m_bitmap);
678 return brush;
679 }
680
isWellMergeable(const DataModel * dm) const681 bool MultiDataModel::isWellMergeable(const DataModel *dm) const
682 {
683 if (!dm->messageCount() || !messageCount())
684 return true;
685
686 int inBothNew = 0;
687 for (int i = 0; i < dm->contextCount(); ++i) {
688 ContextItem *c = dm->contextItem(i);
689 if (MultiContextItem *mc = findContext(c->context())) {
690 for (int j = 0; j < c->messageCount(); ++j) {
691 MessageItem *m = c->messageItem(j);
692 if (mc->findMessage(m->text(), m->comment()) >= 0)
693 ++inBothNew;
694 }
695 }
696 }
697 int newRatio = inBothNew * 100 / dm->messageCount();
698
699 int inBothOld = 0;
700 for (int k = 0; k < contextCount(); ++k) {
701 MultiContextItem *mc = multiContextItem(k);
702 if (ContextItem *c = dm->findContext(mc->context())) {
703 for (int j = 0; j < mc->messageCount(); ++j) {
704 MultiMessageItem *m = mc->multiMessageItem(j);
705 if (c->findMessage(m->text(), m->comment()))
706 ++inBothOld;
707 }
708 }
709 }
710 int oldRatio = inBothOld * 100 / messageCount();
711
712 return newRatio + oldRatio > 90;
713 }
714
append(DataModel * dm,bool readWrite)715 void MultiDataModel::append(DataModel *dm, bool readWrite)
716 {
717 int insCol = modelCount() + 1;
718 m_msgModel->beginInsertColumns(QModelIndex(), insCol, insCol);
719 m_dataModels.append(dm);
720 for (int j = 0; j < contextCount(); ++j) {
721 m_msgModel->beginInsertColumns(m_msgModel->createIndex(j, 0), insCol, insCol);
722 m_multiContextList[j].appendEmptyModel();
723 m_msgModel->endInsertColumns();
724 }
725 m_msgModel->endInsertColumns();
726 int appendedContexts = 0;
727 for (int i = 0; i < dm->contextCount(); ++i) {
728 ContextItem *c = dm->contextItem(i);
729 int mcx = findContextIndex(c->context());
730 if (mcx >= 0) {
731 MultiContextItem *mc = multiContextItem(mcx);
732 mc->assignLastModel(c, readWrite);
733 QList<MessageItem *> appendItems;
734 for (int j = 0; j < c->messageCount(); ++j) {
735 MessageItem *m = c->messageItem(j);
736
737 int msgIdx = -1;
738 if (!m->id().isEmpty()) // id based translation
739 msgIdx = mc->findMessageById(m->id());
740
741 if (msgIdx == -1)
742 msgIdx = mc->findMessage(m->text(), m->comment());
743
744 if (msgIdx >= 0)
745 mc->putMessageItem(msgIdx, m);
746 else
747 appendItems << m;
748 }
749 if (!appendItems.isEmpty()) {
750 int msgCnt = mc->messageCount();
751 m_msgModel->beginInsertRows(m_msgModel->createIndex(mcx, 0),
752 msgCnt, msgCnt + appendItems.size() - 1);
753 mc->appendMessageItems(appendItems);
754 m_msgModel->endInsertRows();
755 m_numMessages += appendItems.size();
756 }
757 } else {
758 m_multiContextList << MultiContextItem(modelCount() - 1, c, readWrite);
759 m_numMessages += c->messageCount();
760 ++appendedContexts;
761 }
762 }
763 if (appendedContexts) {
764 // Do that en block to avoid itemview inefficiency. It doesn't hurt that we
765 // announce the availability of the data "long" after it was actually added.
766 m_msgModel->beginInsertRows(QModelIndex(),
767 contextCount() - appendedContexts, contextCount() - 1);
768 m_msgModel->endInsertRows();
769 }
770 dm->setWritable(readWrite);
771 updateCountsOnAdd(modelCount() - 1, readWrite);
772 connect(dm, SIGNAL(modifiedChanged()), SLOT(onModifiedChanged()));
773 connect(dm, SIGNAL(languageChanged()), SLOT(onLanguageChanged()));
774 connect(dm, SIGNAL(statsChanged(int,int,int,int,int,int)), SIGNAL(statsChanged(int,int,int,int,int,int)));
775 emit modelAppended();
776 }
777
close(int model)778 void MultiDataModel::close(int model)
779 {
780 if (m_dataModels.count() == 1) {
781 closeAll();
782 } else {
783 updateCountsOnRemove(model, isModelWritable(model));
784 int delCol = model + 1;
785 m_msgModel->beginRemoveColumns(QModelIndex(), delCol, delCol);
786 for (int i = m_multiContextList.size(); --i >= 0;) {
787 m_msgModel->beginRemoveColumns(m_msgModel->createIndex(i, 0), delCol, delCol);
788 m_multiContextList[i].removeModel(model);
789 m_msgModel->endRemoveColumns();
790 }
791 delete m_dataModels.takeAt(model);
792 m_msgModel->endRemoveColumns();
793 emit modelDeleted(model);
794 for (int i = m_multiContextList.size(); --i >= 0;) {
795 MultiContextItem &mc = m_multiContextList[i];
796 QModelIndex contextIdx = m_msgModel->createIndex(i, 0);
797 for (int j = mc.messageCount(); --j >= 0;)
798 if (mc.multiMessageItem(j)->isEmpty()) {
799 m_msgModel->beginRemoveRows(contextIdx, j, j);
800 mc.removeMultiMessageItem(j);
801 m_msgModel->endRemoveRows();
802 --m_numMessages;
803 }
804 if (!mc.messageCount()) {
805 m_msgModel->beginRemoveRows(QModelIndex(), i, i);
806 m_multiContextList.removeAt(i);
807 m_msgModel->endRemoveRows();
808 }
809 }
810 onModifiedChanged();
811 }
812 }
813
closeAll()814 void MultiDataModel::closeAll()
815 {
816 m_msgModel->beginResetModel();
817 m_numFinished = 0;
818 m_numEditable = 0;
819 m_numMessages = 0;
820 qDeleteAll(m_dataModels);
821 m_dataModels.clear();
822 m_multiContextList.clear();
823 m_msgModel->endResetModel();
824 emit allModelsDeleted();
825 onModifiedChanged();
826 }
827
828 // XXX this is not needed, yet
moveModel(int oldPos,int newPos)829 void MultiDataModel::moveModel(int oldPos, int newPos)
830 {
831 int delPos = oldPos < newPos ? oldPos : oldPos + 1;
832 m_dataModels.insert(newPos, m_dataModels[oldPos]);
833 m_dataModels.removeAt(delPos);
834 for (int i = 0; i < m_multiContextList.size(); ++i)
835 m_multiContextList[i].moveModel(oldPos, newPos);
836 }
837
prettifyFileNames(const QStringList & names)838 QStringList MultiDataModel::prettifyFileNames(const QStringList &names)
839 {
840 QStringList out;
841
842 foreach (const QString &name, names)
843 out << DataModel::prettifyFileName(name);
844 return out;
845 }
846
condenseFileNames(const QStringList & names)847 QString MultiDataModel::condenseFileNames(const QStringList &names)
848 {
849 if (names.isEmpty())
850 return QString();
851
852 if (names.count() < 2)
853 return names.first();
854
855 QString prefix = names.first();
856 if (prefix.startsWith(QLatin1Char('=')))
857 prefix.remove(0, 1);
858 QString suffix = prefix;
859 for (int i = 1; i < names.count(); ++i) {
860 QString fn = names[i];
861 if (fn.startsWith(QLatin1Char('=')))
862 fn.remove(0, 1);
863 for (int j = 0; j < prefix.length(); ++j)
864 if (fn[j] != prefix[j]) {
865 if (j < prefix.length()) {
866 while (j > 0 && prefix[j - 1].isLetterOrNumber())
867 --j;
868 prefix.truncate(j);
869 }
870 break;
871 }
872 int fnl = fn.length() - 1;
873 int sxl = suffix.length() - 1;
874 for (int k = 0; k <= sxl; ++k)
875 if (fn[fnl - k] != suffix[sxl - k]) {
876 if (k < sxl) {
877 while (k > 0 && suffix[sxl - k + 1].isLetterOrNumber())
878 --k;
879 if (prefix.length() + k > fnl)
880 --k;
881 suffix.remove(0, sxl - k + 1);
882 }
883 break;
884 }
885 }
886 QString ret = prefix + QLatin1Char('{');
887 int pxl = prefix.length();
888 int sxl = suffix.length();
889 for (int j = 0; j < names.count(); ++j) {
890 if (j)
891 ret += QLatin1Char(',');
892 int off = pxl;
893 QString fn = names[j];
894 if (fn.startsWith(QLatin1Char('='))) {
895 ret += QLatin1Char('=');
896 ++off;
897 }
898 ret += fn.mid(off, fn.length() - sxl - off);
899 }
900 ret += QLatin1Char('}') + suffix;
901 return ret;
902 }
903
srcFileNames(bool pretty) const904 QStringList MultiDataModel::srcFileNames(bool pretty) const
905 {
906 QStringList names;
907 foreach (DataModel *dm, m_dataModels)
908 names << (dm->isWritable() ? QString() : QString::fromLatin1("=")) + dm->srcFileName(pretty);
909 return names;
910 }
911
condensedSrcFileNames(bool pretty) const912 QString MultiDataModel::condensedSrcFileNames(bool pretty) const
913 {
914 return condenseFileNames(srcFileNames(pretty));
915 }
916
isModified() const917 bool MultiDataModel::isModified() const
918 {
919 foreach (const DataModel *mdl, m_dataModels)
920 if (mdl->isModified())
921 return true;
922 return false;
923 }
924
onModifiedChanged()925 void MultiDataModel::onModifiedChanged()
926 {
927 bool modified = isModified();
928 if (modified != m_modified) {
929 emit modifiedChanged(modified);
930 m_modified = modified;
931 }
932 }
933
onLanguageChanged()934 void MultiDataModel::onLanguageChanged()
935 {
936 int i = 0;
937 while (sender() != m_dataModels[i])
938 ++i;
939 emit languageChanged(i);
940 }
941
isFileLoaded(const QString & name) const942 int MultiDataModel::isFileLoaded(const QString &name) const
943 {
944 for (int i = 0; i < m_dataModels.size(); ++i)
945 if (m_dataModels[i]->srcFileName() == name)
946 return i;
947 return -1;
948 }
949
findContextIndex(const QString & context) const950 int MultiDataModel::findContextIndex(const QString &context) const
951 {
952 for (int i = 0; i < m_multiContextList.size(); ++i) {
953 const MultiContextItem &mc = m_multiContextList[i];
954 if (mc.context() == context)
955 return i;
956 }
957 return -1;
958 }
959
findContext(const QString & context) const960 MultiContextItem *MultiDataModel::findContext(const QString &context) const
961 {
962 for (int i = 0; i < m_multiContextList.size(); ++i) {
963 const MultiContextItem &mc = m_multiContextList[i];
964 if (mc.context() == context)
965 return const_cast<MultiContextItem *>(&mc);
966 }
967 return 0;
968 }
969
messageItem(const MultiDataIndex & index,int model) const970 MessageItem *MultiDataModel::messageItem(const MultiDataIndex &index, int model) const
971 {
972 if (index.context() < contextCount() && model >= 0 && model < modelCount()) {
973 MultiContextItem *mc = multiContextItem(index.context());
974 if (index.message() < mc->messageCount())
975 return mc->messageItem(model, index.message());
976 }
977 Q_ASSERT(model >= 0 && model < modelCount());
978 Q_ASSERT(index.context() < contextCount());
979 return 0;
980 }
981
setTranslation(const MultiDataIndex & index,const QString & translation)982 void MultiDataModel::setTranslation(const MultiDataIndex &index, const QString &translation)
983 {
984 MessageItem *m = messageItem(index);
985 if (translation == m->translation())
986 return;
987 m->setTranslation(translation);
988 setModified(index.model(), true);
989 emit translationChanged(index);
990 }
991
setFinished(const MultiDataIndex & index,bool finished)992 void MultiDataModel::setFinished(const MultiDataIndex &index, bool finished)
993 {
994 MultiContextItem *mc = multiContextItem(index.context());
995 MultiMessageItem *mm = mc->multiMessageItem(index.message());
996 ContextItem *c = contextItem(index);
997 MessageItem *m = messageItem(index);
998 TranslatorMessage::Type type = m->type();
999 if (type == TranslatorMessage::Unfinished && finished) {
1000 m->setType(TranslatorMessage::Finished);
1001 mm->decrementUnfinishedCount();
1002 if (!mm->countUnfinished()) {
1003 incrementFinishedCount();
1004 mc->incrementFinishedCount();
1005 emit multiContextDataChanged(index);
1006 }
1007 c->incrementFinishedCount();
1008 if (m->danger()) {
1009 c->incrementFinishedDangerCount();
1010 c->decrementUnfinishedDangerCount();
1011 if (!c->unfinishedDangerCount()
1012 || c->finishedCount() == c->nonobsoleteCount())
1013 emit contextDataChanged(index);
1014 } else if (c->finishedCount() == c->nonobsoleteCount()) {
1015 emit contextDataChanged(index);
1016 }
1017 emit messageDataChanged(index);
1018 setModified(index.model(), true);
1019 } else if (type == TranslatorMessage::Finished && !finished) {
1020 m->setType(TranslatorMessage::Unfinished);
1021 mm->incrementUnfinishedCount();
1022 if (mm->countUnfinished() == 1) {
1023 decrementFinishedCount();
1024 mc->decrementFinishedCount();
1025 emit multiContextDataChanged(index);
1026 }
1027 c->decrementFinishedCount();
1028 if (m->danger()) {
1029 c->decrementFinishedDangerCount();
1030 c->incrementUnfinishedDangerCount();
1031 if (c->unfinishedDangerCount() == 1
1032 || c->finishedCount() + 1 == c->nonobsoleteCount())
1033 emit contextDataChanged(index);
1034 } else if (c->finishedCount() + 1 == c->nonobsoleteCount()) {
1035 emit contextDataChanged(index);
1036 }
1037 emit messageDataChanged(index);
1038 setModified(index.model(), true);
1039 }
1040 }
1041
setDanger(const MultiDataIndex & index,bool danger)1042 void MultiDataModel::setDanger(const MultiDataIndex &index, bool danger)
1043 {
1044 ContextItem *c = contextItem(index);
1045 MessageItem *m = messageItem(index);
1046 if (!m->danger() && danger) {
1047 if (m->isFinished()) {
1048 c->incrementFinishedDangerCount();
1049 if (c->finishedDangerCount() == 1)
1050 emit contextDataChanged(index);
1051 } else {
1052 c->incrementUnfinishedDangerCount();
1053 if (c->unfinishedDangerCount() == 1)
1054 emit contextDataChanged(index);
1055 }
1056 emit messageDataChanged(index);
1057 m->setDanger(danger);
1058 } else if (m->danger() && !danger) {
1059 if (m->isFinished()) {
1060 c->decrementFinishedDangerCount();
1061 if (!c->finishedDangerCount())
1062 emit contextDataChanged(index);
1063 } else {
1064 c->decrementUnfinishedDangerCount();
1065 if (!c->unfinishedDangerCount())
1066 emit contextDataChanged(index);
1067 }
1068 emit messageDataChanged(index);
1069 m->setDanger(danger);
1070 }
1071 }
1072
updateCountsOnAdd(int model,bool writable)1073 void MultiDataModel::updateCountsOnAdd(int model, bool writable)
1074 {
1075 for (int i = 0; i < m_multiContextList.size(); ++i) {
1076 MultiContextItem &mc = m_multiContextList[i];
1077 for (int j = 0; j < mc.messageCount(); ++j)
1078 if (MessageItem *m = mc.messageItem(model, j)) {
1079 MultiMessageItem *mm = mc.multiMessageItem(j);
1080 mm->incrementNonnullCount();
1081 if (!m->isObsolete()) {
1082 if (writable) {
1083 if (!mm->countEditable()) {
1084 mc.incrementEditableCount();
1085 incrementEditableCount();
1086 if (m->isFinished()) {
1087 mc.incrementFinishedCount();
1088 incrementFinishedCount();
1089 } else {
1090 mm->incrementUnfinishedCount();
1091 }
1092 } else if (!m->isFinished()) {
1093 if (!mm->isUnfinished()) {
1094 mc.decrementFinishedCount();
1095 decrementFinishedCount();
1096 }
1097 mm->incrementUnfinishedCount();
1098 }
1099 mm->incrementEditableCount();
1100 }
1101 mc.incrementNonobsoleteCount();
1102 mm->incrementNonobsoleteCount();
1103 }
1104 }
1105 }
1106 }
1107
updateCountsOnRemove(int model,bool writable)1108 void MultiDataModel::updateCountsOnRemove(int model, bool writable)
1109 {
1110 for (int i = 0; i < m_multiContextList.size(); ++i) {
1111 MultiContextItem &mc = m_multiContextList[i];
1112 for (int j = 0; j < mc.messageCount(); ++j)
1113 if (MessageItem *m = mc.messageItem(model, j)) {
1114 MultiMessageItem *mm = mc.multiMessageItem(j);
1115 mm->decrementNonnullCount();
1116 if (!m->isObsolete()) {
1117 mm->decrementNonobsoleteCount();
1118 mc.decrementNonobsoleteCount();
1119 if (writable) {
1120 mm->decrementEditableCount();
1121 if (!mm->countEditable()) {
1122 mc.decrementEditableCount();
1123 decrementEditableCount();
1124 if (m->isFinished()) {
1125 mc.decrementFinishedCount();
1126 decrementFinishedCount();
1127 } else {
1128 mm->decrementUnfinishedCount();
1129 }
1130 } else if (!m->isFinished()) {
1131 mm->decrementUnfinishedCount();
1132 if (!mm->isUnfinished()) {
1133 mc.incrementFinishedCount();
1134 incrementFinishedCount();
1135 }
1136 }
1137 }
1138 }
1139 }
1140 }
1141 }
1142
1143 /******************************************************************************
1144 *
1145 * MultiDataModelIterator
1146 *
1147 *****************************************************************************/
1148
MultiDataModelIterator(MultiDataModel * dataModel,int model,int context,int message)1149 MultiDataModelIterator::MultiDataModelIterator(MultiDataModel *dataModel, int model, int context, int message)
1150 : MultiDataIndex(model, context, message), m_dataModel(dataModel)
1151 {
1152 }
1153
operator ++()1154 void MultiDataModelIterator::operator++()
1155 {
1156 Q_ASSERT(isValid());
1157 ++m_message;
1158 if (m_message >= m_dataModel->m_multiContextList.at(m_context).messageCount()) {
1159 ++m_context;
1160 m_message = 0;
1161 }
1162 }
1163
isValid() const1164 bool MultiDataModelIterator::isValid() const
1165 {
1166 return m_context < m_dataModel->m_multiContextList.count();
1167 }
1168
current() const1169 MessageItem *MultiDataModelIterator::current() const
1170 {
1171 return m_dataModel->messageItem(*this);
1172 }
1173
1174
1175 /******************************************************************************
1176 *
1177 * MessageModel
1178 *
1179 *****************************************************************************/
1180
MessageModel(QObject * parent,MultiDataModel * data)1181 MessageModel::MessageModel(QObject *parent, MultiDataModel *data)
1182 : QAbstractItemModel(parent), m_data(data)
1183 {
1184 data->m_msgModel = this;
1185 connect(m_data, SIGNAL(multiContextDataChanged(MultiDataIndex)),
1186 SLOT(multiContextItemChanged(MultiDataIndex)));
1187 connect(m_data, SIGNAL(contextDataChanged(MultiDataIndex)),
1188 SLOT(contextItemChanged(MultiDataIndex)));
1189 connect(m_data, SIGNAL(messageDataChanged(MultiDataIndex)),
1190 SLOT(messageItemChanged(MultiDataIndex)));
1191 }
1192
index(int row,int column,const QModelIndex & parent) const1193 QModelIndex MessageModel::index(int row, int column, const QModelIndex &parent) const
1194 {
1195 if (!parent.isValid())
1196 return createIndex(row, column);
1197 if (!parent.internalId())
1198 return createIndex(row, column, parent.row() + 1);
1199 return QModelIndex();
1200 }
1201
parent(const QModelIndex & index) const1202 QModelIndex MessageModel::parent(const QModelIndex& index) const
1203 {
1204 if (index.internalId())
1205 return createIndex(index.internalId() - 1, 0);
1206 return QModelIndex();
1207 }
1208
multiContextItemChanged(const MultiDataIndex & index)1209 void MessageModel::multiContextItemChanged(const MultiDataIndex &index)
1210 {
1211 QModelIndex idx = createIndex(index.context(), m_data->modelCount() + 2);
1212 emit dataChanged(idx, idx);
1213 }
1214
contextItemChanged(const MultiDataIndex & index)1215 void MessageModel::contextItemChanged(const MultiDataIndex &index)
1216 {
1217 QModelIndex idx = createIndex(index.context(), index.model() + 1);
1218 emit dataChanged(idx, idx);
1219 }
1220
messageItemChanged(const MultiDataIndex & index)1221 void MessageModel::messageItemChanged(const MultiDataIndex &index)
1222 {
1223 QModelIndex idx = createIndex(index.message(), index.model() + 1, index.context() + 1);
1224 emit dataChanged(idx, idx);
1225 }
1226
modelIndex(const MultiDataIndex & index)1227 QModelIndex MessageModel::modelIndex(const MultiDataIndex &index)
1228 {
1229 if (index.message() < 0) // Should be unused case
1230 return createIndex(index.context(), index.model() + 1);
1231 return createIndex(index.message(), index.model() + 1, index.context() + 1);
1232 }
1233
rowCount(const QModelIndex & parent) const1234 int MessageModel::rowCount(const QModelIndex &parent) const
1235 {
1236 if (!parent.isValid())
1237 return m_data->contextCount(); // contexts
1238 if (!parent.internalId()) // messages
1239 return m_data->multiContextItem(parent.row())->messageCount();
1240 return 0;
1241 }
1242
columnCount(const QModelIndex & parent) const1243 int MessageModel::columnCount(const QModelIndex &parent) const
1244 {
1245 if (!parent.isValid())
1246 return m_data->modelCount() + 3;
1247 return m_data->modelCount() + 2;
1248 }
1249
data(const QModelIndex & index,int role) const1250 QVariant MessageModel::data(const QModelIndex &index, int role) const
1251 {
1252 static QVariant pxOn =
1253 QVariant::fromValue(QPixmap(QLatin1String(":/images/s_check_on.png")));
1254 static QVariant pxOff =
1255 QVariant::fromValue(QPixmap(QLatin1String(":/images/s_check_off.png")));
1256 static QVariant pxObsolete =
1257 QVariant::fromValue(QPixmap(QLatin1String(":/images/s_check_obsolete.png")));
1258 static QVariant pxDanger =
1259 QVariant::fromValue(QPixmap(QLatin1String(":/images/s_check_danger.png")));
1260 static QVariant pxWarning =
1261 QVariant::fromValue(QPixmap(QLatin1String(":/images/s_check_warning.png")));
1262 static QVariant pxEmpty =
1263 QVariant::fromValue(QPixmap(QLatin1String(":/images/s_check_empty.png")));
1264
1265 int row = index.row();
1266 int column = index.column() - 1;
1267 if (column < 0)
1268 return QVariant();
1269
1270 int numLangs = m_data->modelCount();
1271
1272 if (role == Qt::ToolTipRole && column < numLangs) {
1273 return tr("Completion status for %1").arg(m_data->model(column)->localizedLanguage());
1274 } else if (index.internalId()) {
1275 // this is a message
1276 int crow = index.internalId() - 1;
1277 MultiContextItem *mci = m_data->multiContextItem(crow);
1278 if (row >= mci->messageCount() || !index.isValid())
1279 return QVariant();
1280
1281 if (role == Qt::DisplayRole || (role == Qt::ToolTipRole && column == numLangs)) {
1282 switch (column - numLangs) {
1283 case 0: // Source text
1284 {
1285 MultiMessageItem *msgItem = mci->multiMessageItem(row);
1286 if (msgItem->text().isEmpty()) {
1287 if (mci->context().isEmpty())
1288 return tr("<file header>");
1289 else
1290 return tr("<context comment>");
1291 }
1292 return msgItem->text().simplified();
1293 }
1294 default: // Status or dummy column => no text
1295 return QVariant();
1296 }
1297 }
1298 else if (role == Qt::DecorationRole && column < numLangs) {
1299 if (MessageItem *msgItem = mci->messageItem(column, row)) {
1300 switch (msgItem->message().type()) {
1301 case TranslatorMessage::Unfinished:
1302 if (msgItem->translation().isEmpty())
1303 return pxEmpty;
1304 if (msgItem->danger())
1305 return pxDanger;
1306 return pxOff;
1307 case TranslatorMessage::Finished:
1308 if (msgItem->danger())
1309 return pxWarning;
1310 return pxOn;
1311 default:
1312 return pxObsolete;
1313 }
1314 }
1315 return QVariant();
1316 }
1317 else if (role == SortRole) {
1318 switch (column - numLangs) {
1319 case 0: // Source text
1320 return mci->multiMessageItem(row)->text().simplified().remove(QLatin1Char('&'));
1321 case 1: // Dummy column
1322 return QVariant();
1323 default:
1324 if (MessageItem *msgItem = mci->messageItem(column, row)) {
1325 int rslt = !msgItem->translation().isEmpty();
1326 if (!msgItem->danger())
1327 rslt |= 2;
1328 if (msgItem->isObsolete())
1329 rslt |= 8;
1330 else if (msgItem->isFinished())
1331 rslt |= 4;
1332 return rslt;
1333 }
1334 return INT_MAX;
1335 }
1336 }
1337 else if (role == Qt::ForegroundRole && column > 0
1338 && mci->multiMessageItem(row)->isObsolete()) {
1339 return QBrush(Qt::darkGray);
1340 }
1341 else if (role == Qt::ForegroundRole && column == numLangs
1342 && mci->multiMessageItem(row)->text().isEmpty()) {
1343 return QBrush(QColor(0, 0xa0, 0xa0));
1344 }
1345 else if (role == Qt::BackgroundRole) {
1346 if (column < numLangs && numLangs != 1)
1347 return m_data->brushForModel(column);
1348 }
1349 } else {
1350 // this is a context
1351 if (row >= m_data->contextCount() || !index.isValid())
1352 return QVariant();
1353
1354 MultiContextItem *mci = m_data->multiContextItem(row);
1355
1356 if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
1357 switch (column - numLangs) {
1358 case 0: // Context
1359 {
1360 if (mci->context().isEmpty())
1361 return tr("<unnamed context>");
1362 return mci->context().simplified();
1363 }
1364 case 1:
1365 {
1366 if (role == Qt::ToolTipRole) {
1367 return tr("%n unfinished message(s) left.", 0,
1368 mci->getNumEditable() - mci->getNumFinished());
1369 }
1370 return QString::asprintf("%d/%d", mci->getNumFinished(), mci->getNumEditable());
1371 }
1372 default:
1373 return QVariant(); // Status => no text
1374 }
1375 }
1376 else if (role == Qt::DecorationRole && column < numLangs) {
1377 if (ContextItem *contextItem = mci->contextItem(column)) {
1378 if (contextItem->isObsolete())
1379 return pxObsolete;
1380 if (contextItem->isFinished())
1381 return contextItem->finishedDangerCount() > 0 ? pxWarning : pxOn;
1382 return contextItem->unfinishedDangerCount() > 0 ? pxDanger : pxOff;
1383 }
1384 return QVariant();
1385 }
1386 else if (role == SortRole) {
1387 switch (column - numLangs) {
1388 case 0: // Context (same as display role)
1389 return mci->context().simplified();
1390 case 1: // Items
1391 return mci->getNumEditable();
1392 default: // Percent
1393 if (ContextItem *contextItem = mci->contextItem(column)) {
1394 int totalItems = contextItem->nonobsoleteCount();
1395 int percent = totalItems ? (100 * contextItem->finishedCount()) / totalItems : 100;
1396 int rslt = percent * (((1 << 28) - 1) / 100) + totalItems;
1397 if (contextItem->isObsolete()) {
1398 rslt |= (1 << 30);
1399 } else if (contextItem->isFinished()) {
1400 rslt |= (1 << 29);
1401 if (!contextItem->finishedDangerCount())
1402 rslt |= (1 << 28);
1403 } else {
1404 if (!contextItem->unfinishedDangerCount())
1405 rslt |= (1 << 28);
1406 }
1407 return rslt;
1408 }
1409 return INT_MAX;
1410 }
1411 }
1412 else if (role == Qt::ForegroundRole && column >= numLangs
1413 && m_data->multiContextItem(row)->isObsolete()) {
1414 return QBrush(Qt::darkGray);
1415 }
1416 else if (role == Qt::ForegroundRole && column == numLangs
1417 && m_data->multiContextItem(row)->context().isEmpty()) {
1418 return QBrush(QColor(0, 0xa0, 0xa0));
1419 }
1420 else if (role == Qt::BackgroundRole) {
1421 if (column < numLangs && numLangs != 1) {
1422 QBrush brush = m_data->brushForModel(column);
1423 if (row & 1) {
1424 brush.setColor(brush.color().darker(108));
1425 }
1426 return brush;
1427 }
1428 }
1429 }
1430 return QVariant();
1431 }
1432
dataIndex(const QModelIndex & index,int model) const1433 MultiDataIndex MessageModel::dataIndex(const QModelIndex &index, int model) const
1434 {
1435 Q_ASSERT(index.isValid());
1436 Q_ASSERT(index.internalId());
1437 return MultiDataIndex(model, index.internalId() - 1, index.row());
1438 }
1439
1440 QT_END_NAMESPACE
1441