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