File indexing completed on 2024-04-21 04:32:04

0001 /*
0002  * Copyright (C) 2010-2015 by Stephen Allewell
0003  * steve.allewell@gmail.com
0004  *
0005  * This program is free software; you can redistribute it and/or modify
0006  * it under the terms of the GNU General Public License as published by
0007  * the Free Software Foundation; either version 2 of the License, or
0008  * (at your option) any later version.
0009  */
0010 
0011 #include "DocumentPalette.h"
0012 
0013 #include <KLocalizedString>
0014 #include <KMessageBox>
0015 
0016 #include "Exceptions.h"
0017 #include "FlossScheme.h"
0018 #include "SchemeManager.h"
0019 #include "SymbolLibrary.h"
0020 #include "SymbolManager.h"
0021 
0022 #include "configuration.h"
0023 
0024 class DocumentPaletteData : public QSharedData
0025 {
0026 public:
0027     DocumentPaletteData();
0028     DocumentPaletteData(const DocumentPaletteData &);
0029     ~DocumentPaletteData();
0030 
0031     static const int version = 103; // added m_symbolLibrary
0032 
0033     QString m_schemeName;
0034     QString m_symbolLibrary;
0035     int m_currentIndex;
0036     QMap<int, DocumentFloss *> m_documentFlosses;
0037 };
0038 
0039 DocumentPaletteData::DocumentPaletteData()
0040     : QSharedData()
0041     , m_schemeName(Configuration::palette_DefaultScheme())
0042     , m_symbolLibrary(QLatin1String("kxstitch"))
0043     , m_currentIndex(-1)
0044 {
0045 }
0046 
0047 DocumentPaletteData::DocumentPaletteData(const DocumentPaletteData &other)
0048     : QSharedData(other)
0049     , m_schemeName(other.m_schemeName)
0050     , m_symbolLibrary(other.m_symbolLibrary)
0051     , m_currentIndex(other.m_currentIndex)
0052 {
0053     for (QMap<int, DocumentFloss *>::const_iterator i = other.m_documentFlosses.constBegin(); i != other.m_documentFlosses.constEnd(); ++i) {
0054         m_documentFlosses.insert(i.key(), new DocumentFloss(other.m_documentFlosses.value(i.key())));
0055     }
0056 }
0057 
0058 DocumentPaletteData::~DocumentPaletteData()
0059 {
0060     qDeleteAll(m_documentFlosses);
0061 }
0062 
0063 DocumentPalette::DocumentPalette()
0064     : d(new DocumentPaletteData)
0065 {
0066 }
0067 
0068 DocumentPalette::DocumentPalette(const DocumentPalette &other)
0069     : d(other.d)
0070 {
0071 }
0072 
0073 DocumentPalette::~DocumentPalette()
0074 {
0075 }
0076 
0077 QString DocumentPalette::schemeName() const
0078 {
0079     return d->m_schemeName;
0080 }
0081 
0082 QString DocumentPalette::symbolLibrary() const
0083 {
0084     return d->m_symbolLibrary;
0085 }
0086 
0087 QMap<int, DocumentFloss *> DocumentPalette::flosses() const
0088 {
0089     return d->m_documentFlosses;
0090 }
0091 
0092 QVector<int> DocumentPalette::sortedFlosses() const
0093 {
0094     int colors = d->m_documentFlosses.count();
0095     QVector<int> sorted = d->m_documentFlosses.keys().toVector();
0096 
0097     bool exchanged;
0098 
0099     do {
0100         exchanged = false;
0101 
0102         for (int i = 0; i < colors - 1; ++i) {
0103             QString flossName1(d->m_documentFlosses.value(sorted[i])->flossName());
0104             QString flossName2(d->m_documentFlosses.value(sorted[i + 1])->flossName());
0105             int length1 = flossName1.length();
0106             int length2 = flossName2.length();
0107 
0108             if (((flossName1 > flossName2) && (length1 >= length2)) || (length1 > length2)) {
0109                 int tmp = sorted.value(i);
0110                 sorted[i] = sorted.value(i + 1);
0111                 sorted[i + 1] = tmp;
0112                 exchanged = true;
0113             }
0114         }
0115     } while (exchanged);
0116 
0117     return sorted;
0118 }
0119 
0120 QList<qint16> DocumentPalette::usedSymbols() const
0121 {
0122     QList<qint16> used;
0123     QList<int> keys = d->m_documentFlosses.keys();
0124 
0125     foreach (int index, keys) {
0126         used << d->m_documentFlosses[index]->stitchSymbol();
0127     }
0128 
0129     return used;
0130 }
0131 
0132 const DocumentFloss *DocumentPalette::currentFloss() const
0133 {
0134     DocumentFloss *documentFloss = nullptr;
0135 
0136     if (d->m_currentIndex != -1) {
0137         documentFloss = d->m_documentFlosses.value(d->m_currentIndex);
0138     }
0139 
0140     return documentFloss;
0141 }
0142 
0143 DocumentFloss *DocumentPalette::floss(int colorIndex)
0144 {
0145     DocumentFloss *documentFloss = nullptr;
0146 
0147     if (d->m_documentFlosses.contains(colorIndex)) {
0148         documentFloss = d->m_documentFlosses.value(colorIndex);
0149     }
0150 
0151     return documentFloss;
0152 }
0153 
0154 int DocumentPalette::currentIndex() const
0155 {
0156     return d->m_currentIndex;
0157 }
0158 
0159 void DocumentPalette::setSchemeName(const QString &schemeName)
0160 {
0161     if (d->m_schemeName == schemeName) {
0162         return;
0163     }
0164 
0165     d->m_schemeName = schemeName;
0166 
0167     FlossScheme *scheme = SchemeManager::scheme(d->m_schemeName);
0168 
0169     for (QMap<int, DocumentFloss *>::const_iterator i = d->m_documentFlosses.constBegin(); i != d->m_documentFlosses.constEnd(); ++i) {
0170         DocumentFloss *documentFloss = i.value();
0171         Floss *floss = scheme->convert(documentFloss->flossColor());
0172         DocumentFloss *replacement = new DocumentFloss(floss->name(),
0173                                                        documentFloss->stitchSymbol(),
0174                                                        documentFloss->backstitchSymbol(),
0175                                                        documentFloss->stitchStrands(),
0176                                                        documentFloss->backstitchStrands());
0177         replacement->setFlossColor(floss->color());
0178         delete documentFloss;
0179         replace(i.key(), replacement);
0180     }
0181 }
0182 
0183 void DocumentPalette::setSymbolLibrary(const QString &symbolLibrary)
0184 {
0185     QList<qint16> indexes = SymbolManager::library(symbolLibrary)->indexes();
0186 
0187     // only change the symbol library if there are enough symbols available
0188     if (d->m_documentFlosses.count() <= indexes.count()) {
0189         d->m_symbolLibrary = symbolLibrary;
0190 
0191         foreach (DocumentFloss *documentFloss, d->m_documentFlosses) {
0192             documentFloss->setStitchSymbol(indexes.takeFirst());
0193         }
0194     }
0195 }
0196 
0197 void DocumentPalette::setCurrentIndex(int currentIndex)
0198 {
0199     d->m_currentIndex = currentIndex;
0200 }
0201 
0202 void DocumentPalette::add(int flossIndex, DocumentFloss *documentFloss)
0203 {
0204     d->m_documentFlosses.insert(flossIndex, documentFloss);
0205 
0206     if (d->m_currentIndex == -1) {
0207         d->m_currentIndex = 0;
0208     }
0209 }
0210 
0211 int DocumentPalette::add(const QColor &srcColor)
0212 {
0213     int colorIndex = -1;
0214 
0215     FlossScheme *scheme = SchemeManager::scheme(d->m_schemeName);
0216     Floss *floss = scheme->find(srcColor);
0217 
0218     if (floss == nullptr) {
0219         floss = scheme->convert(srcColor);
0220     }
0221 
0222     for (QMap<int, DocumentFloss *>::const_iterator i = d->m_documentFlosses.constBegin(); i != d->m_documentFlosses.constEnd(); ++i) {
0223         if (i.value()->flossColor() == floss->color()) {
0224             colorIndex = i.key();
0225         }
0226     }
0227 
0228     if (colorIndex == -1) { // the color hasn't been found in the existing list
0229         colorIndex = freeIndex();
0230         DocumentFloss *documentFloss =
0231             new DocumentFloss(floss->name(), freeSymbol(), Qt::SolidLine, Configuration::palette_StitchStrands(), Configuration::palette_BackstitchStrands());
0232         documentFloss->setFlossColor(floss->color());
0233         add(colorIndex, documentFloss);
0234     }
0235 
0236     return colorIndex;
0237 }
0238 
0239 DocumentFloss *DocumentPalette::remove(int flossIndex)
0240 {
0241     DocumentFloss *documentFloss = d->m_documentFlosses.take(flossIndex);
0242 
0243     if (d->m_documentFlosses.count() == 0) {
0244         d->m_currentIndex = -1;
0245     }
0246 
0247     return documentFloss;
0248 }
0249 
0250 DocumentFloss *DocumentPalette::replace(int flossIndex, DocumentFloss *documentFloss)
0251 {
0252     DocumentFloss *old = d->m_documentFlosses.take(flossIndex);
0253     d->m_documentFlosses.insert(flossIndex, documentFloss);
0254     return old;
0255 }
0256 
0257 void DocumentPalette::swap(int originalIndex, int swappedIndex)
0258 {
0259     DocumentFloss *original = d->m_documentFlosses.take(originalIndex);
0260     d->m_documentFlosses.insert(originalIndex, d->m_documentFlosses.take(swappedIndex));
0261     d->m_documentFlosses.insert(swappedIndex, original);
0262 }
0263 
0264 DocumentPalette &DocumentPalette::operator=(const DocumentPalette &other)
0265 {
0266     d = other.d;
0267     return *this;
0268 }
0269 
0270 bool DocumentPalette::operator==(const DocumentPalette &other) const
0271 {
0272     return d == other.d;
0273 }
0274 
0275 bool DocumentPalette::operator!=(const DocumentPalette &other) const
0276 {
0277     return d != other.d;
0278 }
0279 
0280 QDataStream &operator<<(QDataStream &stream, const DocumentPalette &documentPalette)
0281 {
0282     stream << qint32(DocumentPaletteData::version);
0283     stream << documentPalette.d->m_schemeName;
0284     stream << documentPalette.d->m_symbolLibrary;
0285     stream << qint32(documentPalette.d->m_currentIndex);
0286     stream << qint32(documentPalette.d->m_documentFlosses.count());
0287 
0288     for (QMap<int, DocumentFloss *>::const_iterator i = documentPalette.d->m_documentFlosses.constBegin(); i != documentPalette.d->m_documentFlosses.constEnd();
0289          ++i) {
0290         stream << qint32(i.key());
0291         stream << *i.value();
0292     }
0293 
0294     if (stream.status() != QDataStream::Ok) {
0295         throw FailedWriteFile(stream.status());
0296     }
0297 
0298     return stream;
0299 }
0300 
0301 QDataStream &operator>>(QDataStream &stream, DocumentPalette &documentPalette)
0302 {
0303     qint32 version;
0304     qint32 currentIndex;
0305     qint32 documentPaletteCount;
0306     bool showSymbols;
0307 
0308     qint32 key;
0309     DocumentFloss *documentFloss;
0310 
0311     documentPalette = DocumentPalette();
0312 
0313     stream >> version;
0314 
0315     switch (version) {
0316     case 103:
0317         stream >> documentPalette.d->m_schemeName;
0318         stream >> documentPalette.d->m_symbolLibrary;
0319         stream >> currentIndex;
0320         documentPalette.d->m_currentIndex = currentIndex;
0321         stream >> documentPaletteCount;
0322 
0323         while (documentPaletteCount--) {
0324             documentFloss = new DocumentFloss;
0325             stream >> key;
0326             stream >> *documentFloss;
0327             documentPalette.d->m_documentFlosses.insert(key, documentFloss);
0328         }
0329 
0330         break;
0331 
0332     case 102:
0333         stream >> documentPalette.d->m_schemeName;
0334         documentPalette.d->m_symbolLibrary = QLatin1String("kxstitch");
0335         stream >> currentIndex;
0336         documentPalette.d->m_currentIndex = currentIndex;
0337         stream >> documentPaletteCount;
0338 
0339         while (documentPaletteCount--) {
0340             documentFloss = new DocumentFloss;
0341             stream >> key;
0342             stream >> *documentFloss;
0343             documentPalette.d->m_documentFlosses.insert(key, documentFloss);
0344         }
0345 
0346         break;
0347 
0348     case 101:
0349         stream >> documentPalette.d->m_schemeName;
0350         documentPalette.d->m_symbolLibrary = QLatin1String("kxstitch");
0351         stream >> currentIndex;
0352         documentPalette.d->m_currentIndex = currentIndex;
0353         stream >> showSymbols;
0354         stream >> documentPaletteCount;
0355 
0356         while (documentPaletteCount--) {
0357             documentFloss = new DocumentFloss;
0358             stream >> key;
0359             stream >> *documentFloss;
0360             documentPalette.d->m_documentFlosses.insert(key, documentFloss);
0361         }
0362 
0363         break;
0364 
0365     case 100:
0366         stream >> documentPalette.d->m_schemeName;
0367         documentPalette.d->m_symbolLibrary = QLatin1String("kxstitch");
0368         stream >> currentIndex;
0369         documentPalette.d->m_currentIndex = currentIndex;
0370         stream >> showSymbols;
0371         stream >> documentPaletteCount;
0372 
0373         while (documentPaletteCount--) {
0374             documentFloss = new DocumentFloss;
0375             stream >> key;
0376             stream >> *documentFloss;
0377             documentFloss->setStitchSymbol(documentPalette.freeSymbol());
0378             documentPalette.d->m_documentFlosses.insert(key, documentFloss);
0379         }
0380 
0381         break;
0382 
0383     default:
0384         throw InvalidFileVersion(QString(i18n("Palette version %1", version)));
0385         break;
0386     }
0387 
0388     if (stream.status() != QDataStream::Ok) {
0389         throw FailedReadFile(QString(i18n("Failed reading palette")));
0390     }
0391 
0392     // test DocumentFloss symbol indexes exist in the library
0393     QList<qint16> indexes = SymbolManager::library(documentPalette.symbolLibrary())->indexes();
0394     QList<DocumentFloss *> missingSymbols;
0395     QList<int> keys = documentPalette.d->m_documentFlosses.keys();
0396 
0397     foreach (int key, keys) {
0398         documentFloss = documentPalette.d->m_documentFlosses.value(key);
0399         qint16 symbol = documentFloss->stitchSymbol();
0400 
0401         if (indexes.contains(symbol)) {
0402             indexes.removeOne(symbol);
0403         } else {
0404             missingSymbols.append(documentFloss);
0405         }
0406     }
0407 
0408     // missingSymbols will contain pointers to DocumentFloss where the symbol index is not in the symbol library
0409     // check there is a sufficient quantity of symbols to allocate to the remaining flosses
0410     if (missingSymbols.count() > indexes.count()) {
0411         if (KMessageBox::Cancel
0412             == KMessageBox::warningContinueCancel(
0413                 nullptr,
0414                 QString(i18n("There are insufficient symbols available in the symbol library for this pattern. An extra %1 are required.",
0415                              missingSymbols.count() - indexes.count())))) {
0416             throw FailedReadFile(QString(i18n("Canceled: Insufficient symbols available")));
0417         }
0418     }
0419 
0420     // iterate the list and allocate a free symbol to the missing ones.
0421     // if there is insufficient symbols to allocate, empty symbols will be assigned.
0422     QList<QString> emptySymbols;
0423 
0424     foreach (DocumentFloss *const documentFloss, missingSymbols) {
0425         documentFloss->setStitchSymbol(documentPalette.freeSymbol());
0426         if (documentFloss->stitchSymbol() == -1) {
0427             emptySymbols.append(documentFloss->flossName());
0428         }
0429     }
0430 
0431     if (int count = missingSymbols.count()) {
0432         // display an information box to show symbols have been allocated
0433         QString information(i18np("The following floss color has had its symbol\nreplaced because it did not exist in the symbol library.\n\n",
0434                                   "The following floss colors have had their symbols\nreplaced because they did not exist in the symbol library.\n\n",
0435                                   count));
0436 
0437         foreach (DocumentFloss *documentFloss, missingSymbols) {
0438             information += QString::fromLatin1("%1\n").arg(documentFloss->flossName());
0439         }
0440 
0441         if (int count = emptySymbols.count()) {
0442             information += QString(i18np("The following floss color has had an empty symbol assigned.\n\n",
0443                                          "The following floss colors have had an empty symbol assigned.\n\n",
0444                                          count));
0445 
0446             foreach (const QString &name, emptySymbols) {
0447                 information += QString::fromLatin1("%1\n").arg(name);
0448             }
0449         }
0450 
0451         information += QString(i18np("\nYou may want to check this is suitable.", "\nYou may want to check these are suitable.", count));
0452 
0453         KMessageBox::information(nullptr, information);
0454     }
0455 
0456     return stream;
0457 }
0458 
0459 int DocumentPalette::freeIndex() const
0460 {
0461     int i = 0;
0462 
0463     while (d->m_documentFlosses.contains(i)) {
0464         ++i;
0465     }
0466 
0467     return i;
0468 }
0469 
0470 qint16 DocumentPalette::freeSymbol() const
0471 {
0472     QList<qint16> indexes = SymbolManager::library(d->m_symbolLibrary)->indexes();
0473     QList<int> keys = d->m_documentFlosses.keys();
0474 
0475     foreach (int index, keys) {
0476         indexes.removeOne(d->m_documentFlosses[index]->stitchSymbol());
0477     }
0478 
0479     return (indexes.isEmpty() ? -1 : indexes.first());
0480 }