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