1 /*
2  * Copyright (C) 2010-2015 by Stephen Allewell
3  * steve.allewell@gmail.com
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  */
10 
11 
12 #include "DocumentPalette.h"
13 
14 #include <KLocalizedString>
15 #include <KMessageBox>
16 
17 #include "Exceptions.h"
18 #include "FlossScheme.h"
19 #include "SchemeManager.h"
20 #include "SymbolLibrary.h"
21 #include "SymbolManager.h"
22 
23 #include "configuration.h"
24 
25 
26 class DocumentPaletteData : public QSharedData
27 {
28 public:
29     DocumentPaletteData();
30     DocumentPaletteData(const DocumentPaletteData &);
31     ~DocumentPaletteData();
32 
33     static const int version = 103; // added m_symbolLibrary
34 
35     QString                     m_schemeName;
36     QString                     m_symbolLibrary;
37     int                         m_currentIndex;
38     QMap<int, DocumentFloss *>  m_documentFlosses;
39 };
40 
41 
DocumentPaletteData()42 DocumentPaletteData::DocumentPaletteData()
43     :   QSharedData(),
44         m_schemeName(Configuration::palette_DefaultScheme()),
45         m_symbolLibrary(QLatin1String("kxstitch")),
46         m_currentIndex(-1)
47 {
48 }
49 
50 
DocumentPaletteData(const DocumentPaletteData & other)51 DocumentPaletteData::DocumentPaletteData(const DocumentPaletteData &other)
52     :   QSharedData(other),
53         m_schemeName(other.m_schemeName),
54         m_symbolLibrary(other.m_symbolLibrary),
55         m_currentIndex(other.m_currentIndex)
56 {
57     for (QMap<int, DocumentFloss*>::const_iterator i = other.m_documentFlosses.constBegin() ; i != other.m_documentFlosses.constEnd() ; ++i) {
58         m_documentFlosses.insert(i.key(), new DocumentFloss(other.m_documentFlosses.value(i.key())));
59     }
60 }
61 
62 
~DocumentPaletteData()63 DocumentPaletteData::~DocumentPaletteData()
64 {
65     qDeleteAll(m_documentFlosses);
66 }
67 
68 
DocumentPalette()69 DocumentPalette::DocumentPalette()
70     :   d(new DocumentPaletteData)
71 {
72 }
73 
74 
DocumentPalette(const DocumentPalette & other)75 DocumentPalette::DocumentPalette(const DocumentPalette &other)
76     :   d(other.d)
77 {
78 }
79 
80 
~DocumentPalette()81 DocumentPalette::~DocumentPalette()
82 {
83 }
84 
85 
schemeName() const86 QString DocumentPalette::schemeName() const
87 {
88     return d->m_schemeName;
89 }
90 
91 
symbolLibrary() const92 QString DocumentPalette::symbolLibrary() const
93 {
94     return d->m_symbolLibrary;
95 }
96 
97 
flosses() const98 QMap<int, DocumentFloss *> DocumentPalette::flosses() const
99 {
100     return d->m_documentFlosses;
101 }
102 
103 
sortedFlosses() const104 QVector<int> DocumentPalette::sortedFlosses() const
105 {
106     int colors = d->m_documentFlosses.count();
107     QVector<int> sorted = d->m_documentFlosses.keys().toVector();
108 
109     bool exchanged;
110 
111     do {
112         exchanged = false;
113 
114         for (int i = 0 ; i < colors - 1 ; ++i) {
115             QString flossName1(d->m_documentFlosses.value(sorted[i])->flossName());
116             QString flossName2(d->m_documentFlosses.value(sorted[i + 1])->flossName());
117             int length1 = flossName1.length();
118             int length2 = flossName2.length();
119 
120             if (((flossName1 > flossName2) && (length1 >= length2)) || (length1 > length2)) {
121                 int tmp = sorted.value(i);
122                 sorted[i] = sorted.value(i + 1);
123                 sorted[i + 1] = tmp;
124                 exchanged = true;
125             }
126         }
127     } while (exchanged);
128 
129     return sorted;
130 }
131 
132 
usedSymbols() const133 QList<qint16> DocumentPalette::usedSymbols() const
134 {
135     QList<qint16> used;
136     QList<int> keys = d->m_documentFlosses.keys();
137 
138     foreach (int index, keys) {
139         used << d->m_documentFlosses[index]->stitchSymbol();
140     }
141 
142     return used;
143 }
144 
145 
currentFloss() const146 const DocumentFloss *DocumentPalette::currentFloss() const
147 {
148     DocumentFloss *documentFloss = nullptr;
149 
150     if (d->m_currentIndex != -1) {
151         documentFloss = d->m_documentFlosses.value(d->m_currentIndex);
152     }
153 
154     return documentFloss;
155 }
156 
157 
floss(int colorIndex)158 DocumentFloss *DocumentPalette::floss(int colorIndex)
159 {
160     DocumentFloss *documentFloss = nullptr;
161 
162     if (d->m_documentFlosses.contains(colorIndex)) {
163         documentFloss = d->m_documentFlosses.value(colorIndex);
164     }
165 
166     return documentFloss;
167 }
168 
169 
currentIndex() const170 int DocumentPalette::currentIndex() const
171 {
172     return d->m_currentIndex;
173 }
174 
175 
setSchemeName(const QString & schemeName)176 void DocumentPalette::setSchemeName(const QString &schemeName)
177 {
178     if (d->m_schemeName == schemeName) {
179         return;
180     }
181 
182     d->m_schemeName = schemeName;
183 
184     FlossScheme *scheme = SchemeManager::scheme(d->m_schemeName);
185 
186     for (QMap<int, DocumentFloss*>::const_iterator i = d->m_documentFlosses.constBegin() ; i != d->m_documentFlosses.constEnd() ; ++i) {
187         DocumentFloss *documentFloss = i.value();
188         Floss *floss = scheme->convert(documentFloss->flossColor());
189         DocumentFloss *replacement = new DocumentFloss(floss->name(), documentFloss->stitchSymbol(), documentFloss->backstitchSymbol(), documentFloss->stitchStrands(), documentFloss->backstitchStrands());
190         replacement->setFlossColor(floss->color());
191         delete documentFloss;
192         replace(i.key(), replacement);
193     }
194 }
195 
196 
setSymbolLibrary(const QString & symbolLibrary)197 void DocumentPalette::setSymbolLibrary(const QString &symbolLibrary)
198 {
199     QList<qint16> indexes = SymbolManager::library(symbolLibrary)->indexes();
200 
201     // only change the symbol library if there are enough symbols available
202     if (d->m_documentFlosses.count() <= indexes.count()) {
203         d->m_symbolLibrary = symbolLibrary;
204 
205         foreach (DocumentFloss *documentFloss, d->m_documentFlosses) {
206             documentFloss->setStitchSymbol(indexes.takeFirst());
207         }
208     }
209 }
210 
211 
setCurrentIndex(int currentIndex)212 void DocumentPalette::setCurrentIndex(int currentIndex)
213 {
214     d->m_currentIndex = currentIndex;
215 }
216 
217 
add(int flossIndex,DocumentFloss * documentFloss)218 void DocumentPalette::add(int flossIndex, DocumentFloss *documentFloss)
219 {
220     d->m_documentFlosses.insert(flossIndex, documentFloss);
221 
222     if (d->m_currentIndex == -1) {
223         d->m_currentIndex = 0;
224     }
225 }
226 
227 
add(const QColor & srcColor)228 int DocumentPalette::add(const QColor &srcColor)
229 {
230     int colorIndex = -1;
231 
232     FlossScheme *scheme = SchemeManager::scheme(d->m_schemeName);
233     Floss *floss = scheme->find(srcColor);
234 
235     if (floss == nullptr) {
236         floss = scheme->convert(srcColor);
237     }
238 
239     for (QMap<int, DocumentFloss*>::const_iterator i = d->m_documentFlosses.constBegin() ; i != d->m_documentFlosses.constEnd() ; ++i) {
240         if (i.value()->flossColor() == floss->color()) {
241             colorIndex = i.key();
242         }
243     }
244 
245     if (colorIndex == -1) { // the color hasn't been found in the existing list
246         colorIndex = freeIndex();
247         DocumentFloss *documentFloss = new DocumentFloss(floss->name(), freeSymbol(), Qt::SolidLine, Configuration::palette_StitchStrands(), Configuration::palette_BackstitchStrands());
248         documentFloss->setFlossColor(floss->color());
249         add(colorIndex, documentFloss);
250     }
251 
252     return colorIndex;
253 }
254 
255 
remove(int flossIndex)256 DocumentFloss *DocumentPalette::remove(int flossIndex)
257 {
258     DocumentFloss *documentFloss = d->m_documentFlosses.take(flossIndex);
259 
260     if (d->m_documentFlosses.count() == 0) {
261         d->m_currentIndex = -1;
262     }
263 
264     return documentFloss;
265 }
266 
267 
replace(int flossIndex,DocumentFloss * documentFloss)268 DocumentFloss *DocumentPalette::replace(int flossIndex, DocumentFloss *documentFloss)
269 {
270     DocumentFloss *old = d->m_documentFlosses.take(flossIndex);
271     d->m_documentFlosses.insert(flossIndex, documentFloss);
272     return old;
273 }
274 
275 
swap(int originalIndex,int swappedIndex)276 void DocumentPalette::swap(int originalIndex, int swappedIndex)
277 {
278     DocumentFloss *original = d->m_documentFlosses.take(originalIndex);
279     d->m_documentFlosses.insert(originalIndex, d->m_documentFlosses.take(swappedIndex));
280     d->m_documentFlosses.insert(swappedIndex, original);
281 }
282 
283 
operator =(const DocumentPalette & other)284 DocumentPalette &DocumentPalette::operator=(const DocumentPalette &other)
285 {
286     d = other.d;
287     return *this;
288 }
289 
290 
operator ==(const DocumentPalette & other) const291 bool DocumentPalette::operator==(const DocumentPalette &other) const
292 {
293     return d == other.d;
294 }
295 
296 
operator !=(const DocumentPalette & other) const297 bool DocumentPalette::operator!=(const DocumentPalette &other) const
298 {
299     return d != other.d;
300 }
301 
302 
operator <<(QDataStream & stream,const DocumentPalette & documentPalette)303 QDataStream &operator<<(QDataStream &stream, const DocumentPalette &documentPalette)
304 {
305     stream << qint32(DocumentPaletteData::version);
306     stream << documentPalette.d->m_schemeName;
307     stream << documentPalette.d->m_symbolLibrary;
308     stream << qint32(documentPalette.d->m_currentIndex);
309     stream << qint32(documentPalette.d->m_documentFlosses.count());
310 
311     for (QMap<int, DocumentFloss*>::const_iterator i = documentPalette.d->m_documentFlosses.constBegin() ; i != documentPalette.d->m_documentFlosses.constEnd() ; ++i) {
312         stream << qint32(i.key());
313         stream << *i.value();
314     }
315 
316     if (stream.status() != QDataStream::Ok) {
317         throw FailedWriteFile(stream.status());
318     }
319 
320     return stream;
321 }
322 
323 
operator >>(QDataStream & stream,DocumentPalette & documentPalette)324 QDataStream &operator>>(QDataStream &stream, DocumentPalette &documentPalette)
325 {
326     qint32 version;
327     qint32 currentIndex;
328     qint32 documentPaletteCount;
329     bool showSymbols;
330 
331     qint32 key;
332     DocumentFloss *documentFloss;
333 
334     documentPalette = DocumentPalette();
335 
336     stream >> version;
337 
338     switch (version) {
339     case 103:
340         stream >> documentPalette.d->m_schemeName;
341         stream >> documentPalette.d->m_symbolLibrary;
342         stream >> currentIndex;
343         documentPalette.d->m_currentIndex = currentIndex;
344         stream >> documentPaletteCount;
345 
346         while (documentPaletteCount--) {
347             documentFloss = new DocumentFloss;
348             stream >> key;
349             stream >> *documentFloss;
350             documentPalette.d->m_documentFlosses.insert(key, documentFloss);
351         }
352 
353         break;
354 
355     case 102:
356         stream >> documentPalette.d->m_schemeName;
357         documentPalette.d->m_symbolLibrary = QLatin1String("kxstitch");
358         stream >> currentIndex;
359         documentPalette.d->m_currentIndex = currentIndex;
360         stream >> documentPaletteCount;
361 
362         while (documentPaletteCount--) {
363             documentFloss = new DocumentFloss;
364             stream >> key;
365             stream >> *documentFloss;
366             documentPalette.d->m_documentFlosses.insert(key, documentFloss);
367         }
368 
369         break;
370 
371     case 101:
372         stream >> documentPalette.d->m_schemeName;
373         documentPalette.d->m_symbolLibrary = QLatin1String("kxstitch");
374         stream >> currentIndex;
375         documentPalette.d->m_currentIndex = currentIndex;
376         stream >> showSymbols;
377         stream >> documentPaletteCount;
378 
379         while (documentPaletteCount--) {
380             documentFloss = new DocumentFloss;
381             stream >> key;
382             stream >> *documentFloss;
383             documentPalette.d->m_documentFlosses.insert(key, documentFloss);
384         }
385 
386         break;
387 
388     case 100:
389         stream >> documentPalette.d->m_schemeName;
390         documentPalette.d->m_symbolLibrary = QLatin1String("kxstitch");
391         stream >> currentIndex;
392         documentPalette.d->m_currentIndex = currentIndex;
393         stream >> showSymbols;
394         stream >> documentPaletteCount;
395 
396         while (documentPaletteCount--) {
397             documentFloss = new DocumentFloss;
398             stream >> key;
399             stream >> *documentFloss;
400             documentFloss->setStitchSymbol(documentPalette.freeSymbol());
401             documentPalette.d->m_documentFlosses.insert(key, documentFloss);
402         }
403 
404         break;
405 
406     default:
407         throw InvalidFileVersion(QString(i18n("Palette version %1", version)));
408         break;
409     }
410 
411     if (stream.status() != QDataStream::Ok) {
412         throw FailedReadFile(QString(i18n("Failed reading palette")));
413     }
414 
415     // test DocumentFloss symbol indexes exist in the library
416     QList<qint16> indexes = SymbolManager::library(documentPalette.symbolLibrary())->indexes();
417     QList<DocumentFloss *> missingSymbols;
418     QList<int> keys = documentPalette.d->m_documentFlosses.keys();
419 
420     foreach (int key, keys) {
421         documentFloss = documentPalette.d->m_documentFlosses.value(key);
422         qint16 symbol = documentFloss->stitchSymbol();
423 
424         if (indexes.contains(symbol)) {
425             indexes.removeOne(symbol);
426         } else {
427             missingSymbols.append(documentFloss);
428         }
429     }
430 
431     // missingSymbols will contain pointers to DocumentFloss where the symbol index is not in the symbol library
432     // check there is a sufficient quantity of symbols to allocate to the remaining flosses
433     if (missingSymbols.count() > indexes.count()) {
434         if (KMessageBox::Cancel == KMessageBox::warningContinueCancel(nullptr, QString(i18n("There are insufficient symbols available in the symbol library for this pattern. An extra %1 are required.", missingSymbols.count() - indexes.count())))) {
435             throw FailedReadFile(QString(i18n("Canceled: Insufficient symbols available")));
436         }
437     }
438 
439     // iterate the list and allocate a free symbol to the missing ones.
440     // if there is insufficient symbols to allocate, empty symbols will be assigned.
441     QList<QString> emptySymbols;
442 
443     foreach (DocumentFloss *const documentFloss, missingSymbols) {
444         documentFloss->setStitchSymbol(documentPalette.freeSymbol());
445         if (documentFloss->stitchSymbol() == -1) {
446             emptySymbols.append(documentFloss->flossName());
447         }
448     }
449 
450     if (int count = missingSymbols.count()) {
451         // display an information box to show symbols have been allocated
452         QString information(i18np("The following floss color has had its symbol\nreplaced because it did not exist in the symbol library.\n\n",
453                                   "The following floss colors have had their symbols\nreplaced because they did not exist in the symbol library.\n\n",
454                                   count));
455 
456         foreach (DocumentFloss *documentFloss, missingSymbols) {
457             information += QString::fromLatin1("%1\n").arg(documentFloss->flossName());
458         }
459 
460         if (int count = emptySymbols.count()) {
461             information += QString(i18np("The following floss color has had an empty symbol assigned.\n\n",
462                                          "The following floss colors have had an empty symbol assigned.\n\n",
463                                          count));
464 
465             foreach (const QString &name, emptySymbols) {
466                 information += QString::fromLatin1("%1\n").arg(name);
467             }
468         }
469 
470         information += QString(i18np("\nYou may want to check this is suitable.",
471                                      "\nYou may want to check these are suitable.",
472                                      count));
473 
474         KMessageBox::information(nullptr, information);
475     }
476 
477     return stream;
478 }
479 
480 
freeIndex() const481 int DocumentPalette::freeIndex() const
482 {
483     int i = 0;
484 
485     while (d->m_documentFlosses.contains(i)) {
486         ++i;
487     }
488 
489     return i;
490 }
491 
492 
freeSymbol() const493 qint16 DocumentPalette::freeSymbol() const
494 {
495     QList<qint16> indexes = SymbolManager::library(d->m_symbolLibrary)->indexes();
496     QList<int> keys = d->m_documentFlosses.keys();
497 
498     foreach (int index, keys) {
499         indexes.removeOne(d->m_documentFlosses[index]->stitchSymbol());
500     }
501 
502     return (indexes.isEmpty() ? -1 : indexes.first());
503 }
504