1 /* This file is part of the KDE project
2 * Copyright (C) 2008 Thomas Zander <zander@kde.org>
3 * Copyright (C) 2011 C. Boemann <cbo@boemann.dk>
4 * Copyright (C) 2011-2012 Pierre Stirnweiss <pstirnweiss@googlemail.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB. If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21 #include "StylesModel.h"
22
23 #include <KoStyleThumbnailer.h>
24 #include <KoStyleManager.h>
25 #include <KoParagraphStyle.h>
26 #include <KoCharacterStyle.h>
27
28 #include <QImage>
29 #include <QList>
30 #include <QSharedPointer>
31 #include <KisSignalMapper.h>
32 #include <QCollator>
33
34 #include <klocalizedstring.h>
35 #include <QDebug>
36
StylesModel(KoStyleManager * manager,AbstractStylesModel::Type modelType,QObject * parent)37 StylesModel::StylesModel(KoStyleManager *manager, AbstractStylesModel::Type modelType, QObject *parent)
38 : AbstractStylesModel(parent)
39 , m_styleManager(0)
40 , m_currentParagraphStyle(0)
41 , m_defaultCharacterStyle(0)
42 , m_styleMapper(new KisSignalMapper(this))
43 , m_provideStyleNone(false)
44 {
45 m_modelType = modelType;
46 setStyleManager(manager);
47 //Create a default characterStyle for the preview of "None" character style
48 if (m_modelType == StylesModel::CharacterStyle) {
49 m_defaultCharacterStyle = new KoCharacterStyle();
50 m_defaultCharacterStyle->setStyleId(NoneStyleId);
51 m_defaultCharacterStyle->setName(i18n("None"));
52 m_defaultCharacterStyle->setFontPointSize(12);
53
54 m_provideStyleNone = true;
55 }
56
57 connect(m_styleMapper, SIGNAL(mapped(int)), this, SLOT(updateName(int)));
58 }
59
~StylesModel()60 StylesModel::~StylesModel()
61 {
62 delete m_currentParagraphStyle;
63 delete m_defaultCharacterStyle;
64 }
65
index(int row,int column,const QModelIndex & parent) const66 QModelIndex StylesModel::index(int row, int column, const QModelIndex &parent) const
67 {
68 if (row < 0 || column != 0) {
69 return QModelIndex();
70 }
71
72 if (!parent.isValid()) {
73 if (row >= m_styleList.count()) {
74 return QModelIndex();
75 }
76 return createIndex(row, column, m_styleList[row]);
77 }
78 return QModelIndex();
79 }
80
parent(const QModelIndex & child) const81 QModelIndex StylesModel::parent(const QModelIndex &child) const
82 {
83 Q_UNUSED(child);
84 return QModelIndex();
85 }
86
rowCount(const QModelIndex & parent) const87 int StylesModel::rowCount(const QModelIndex &parent) const
88 {
89 if (!parent.isValid()) {
90 return m_styleList.count();
91 }
92 return 0;
93 }
94
columnCount(const QModelIndex & parent) const95 int StylesModel::columnCount(const QModelIndex &parent) const
96 {
97 Q_UNUSED(parent);
98 return 1;
99 }
100
data(const QModelIndex & index,int role) const101 QVariant StylesModel::data(const QModelIndex &index, int role) const
102 {
103 if (!index.isValid()) {
104 return QVariant();
105 }
106
107 int id = (int)index.internalId();
108 switch (role) {
109 case Qt::DisplayRole: {
110 return QVariant();
111 }
112 case Qt::DecorationRole: {
113 if (!m_styleThumbnailer) {
114 return QPixmap();
115 }
116 if (m_modelType == StylesModel::ParagraphStyle) {
117 KoParagraphStyle *paragStyle = m_styleManager->paragraphStyle(id);
118 if (paragStyle) {
119 return m_styleThumbnailer->thumbnail(paragStyle);
120 }
121 if (!paragStyle && m_draftParStyleList.contains(id)) {
122 return m_styleThumbnailer->thumbnail(m_draftParStyleList[id]);
123 }
124 } else {
125 KoCharacterStyle *usedStyle = 0;
126 if (id == NoneStyleId) {
127 usedStyle = static_cast<KoCharacterStyle *>(m_currentParagraphStyle);
128 if (!usedStyle) {
129 usedStyle = m_defaultCharacterStyle;
130 }
131 usedStyle->setName(i18n("None"));
132 if (usedStyle->styleId() >= 0) { //if the styleId is NoneStyleId, we are using the default character style
133 usedStyle->setStyleId(-usedStyle->styleId()); //this style is not managed by the styleManager but its styleId will be used in the thumbnail cache as part of the key.
134 }
135 return m_styleThumbnailer->thumbnail(usedStyle);
136 } else {
137 usedStyle = m_styleManager->characterStyle(id);
138 if (usedStyle) {
139 return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle);
140 }
141 if (!usedStyle && m_draftCharStyleList.contains(id)) {
142 return m_styleThumbnailer->thumbnail(m_draftCharStyleList[id]);
143 }
144 }
145 }
146 break;
147 }
148 case Qt::SizeHintRole: {
149 return QVariant(QSize(250, 48));
150 }
151 default: break;
152 };
153 return QVariant();
154 }
155
flags(const QModelIndex & index) const156 Qt::ItemFlags StylesModel::flags(const QModelIndex &index) const
157 {
158 if (!index.isValid()) {
159 return 0;
160 }
161 return (Qt::ItemIsSelectable | Qt::ItemIsEnabled);
162 }
163
setCurrentParagraphStyle(int styleId)164 void StylesModel::setCurrentParagraphStyle(int styleId)
165 {
166 if (!m_styleManager || m_currentParagraphStyle == m_styleManager->paragraphStyle(styleId) || !m_styleManager->paragraphStyle(styleId)) {
167 return; //TODO do we create a default paragraphStyle? use the styleManager default?
168 }
169 if (m_currentParagraphStyle) {
170 delete m_currentParagraphStyle;
171 m_currentParagraphStyle = 0;
172 }
173 m_currentParagraphStyle = m_styleManager->paragraphStyle(styleId)->clone();
174 }
175
setProvideStyleNone(bool provide)176 void StylesModel::setProvideStyleNone(bool provide)
177 {
178 if (m_modelType == StylesModel::CharacterStyle) {
179 m_provideStyleNone = provide;
180 }
181 }
182
indexOf(const KoCharacterStyle * style) const183 QModelIndex StylesModel::indexOf(const KoCharacterStyle *style) const
184 {
185 if (style) {
186 return createIndex(m_styleList.indexOf(style->styleId()), 0, style->styleId());
187 }
188 else {
189 return QModelIndex();
190 }
191 }
192
stylePreview(int row,const QSize & size)193 QImage StylesModel::stylePreview(int row, const QSize &size)
194 {
195 if (!m_styleManager || !m_styleThumbnailer) {
196 return QImage();
197 }
198 if (m_modelType == StylesModel::ParagraphStyle) {
199 KoParagraphStyle *usedStyle = 0;
200 usedStyle = m_styleManager->paragraphStyle(index(row).internalId());
201 if (usedStyle) {
202 return m_styleThumbnailer->thumbnail(usedStyle, size);
203 }
204 if (!usedStyle && m_draftParStyleList.contains(index(row).internalId())) {
205 return m_styleThumbnailer->thumbnail(m_draftParStyleList[index(row).internalId()], size);
206 }
207 } else {
208 KoCharacterStyle *usedStyle = 0;
209 if (index(row).internalId() == (quintptr)NoneStyleId) {
210 usedStyle = static_cast<KoCharacterStyle *>(m_currentParagraphStyle);
211 if (!usedStyle) {
212 usedStyle = m_defaultCharacterStyle;
213 }
214 usedStyle->setName(i18n("None"));
215 if (usedStyle->styleId() >= 0) {
216 usedStyle->setStyleId(-usedStyle->styleId()); //this style is not managed by the styleManager but its styleId will be used in the thumbnail cache as part of the key.
217 }
218 return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle, size);
219 } else {
220 usedStyle = m_styleManager->characterStyle(index(row).internalId());
221 if (usedStyle) {
222 return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle, size);
223 }
224 if (!usedStyle && m_draftCharStyleList.contains(index(row).internalId())) {
225 return m_styleThumbnailer->thumbnail(m_draftCharStyleList[index(row).internalId()], m_currentParagraphStyle, size);
226 }
227 }
228 }
229 return QImage();
230 }
231 /*
232 QImage StylesModel::stylePreview(QModelIndex &index, const QSize &size)
233 {
234 if (!m_styleManager || !m_styleThumbnailer) {
235 return QImage();
236 }
237 if (m_modelType == StylesModel::ParagraphStyle) {
238 KoParagraphStyle *usedStyle = 0;
239 usedStyle = m_styleManager->paragraphStyle(index.internalId());
240 if (usedStyle) {
241 return m_styleThumbnailer->thumbnail(usedStyle, size);
242 }
243 if (!usedStyle && m_draftParStyleList.contains(index.internalId())) {
244 return m_styleThumbnailer->thumbnail(m_draftParStyleList[index.internalId()], size);
245 }
246 }
247 else {
248 KoCharacterStyle *usedStyle = 0;
249 if (index.internalId() == NoneStyleId) {
250 usedStyle = static_cast<KoCharacterStyle*>(m_currentParagraphStyle);
251 if (!usedStyle) {
252 usedStyle = m_defaultCharacterStyle;
253 }
254 usedStyle->setName(i18n("None"));
255 if (usedStyle->styleId() >= 0) {
256 usedStyle->setStyleId(-usedStyle->styleId()); //this style is not managed by the styleManager but its styleId will be used in the thumbnail cache as part of the key.
257 }
258 return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle, size);
259 }
260 else {
261 usedStyle = m_styleManager->characterStyle(index.internalId());
262 if (usedStyle) {
263 return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle, size);
264 }
265 if (!usedStyle && m_draftCharStyleList.contains(index.internalId())) {
266 return m_styleThumbnailer->thumbnail(m_draftCharStyleList[index.internalId()],m_currentParagraphStyle, size);
267 }
268 }
269 }
270 return QImage();
271 }
272 */
setStyleManager(KoStyleManager * sm)273 void StylesModel::setStyleManager(KoStyleManager *sm)
274 {
275 if (sm == m_styleManager) {
276 return;
277 }
278 if (m_styleManager) {
279 disconnect(sm, SIGNAL(styleAdded(KoParagraphStyle*)), this, SLOT(addParagraphStyle(KoParagraphStyle*)));
280 disconnect(sm, SIGNAL(styleAdded(KoCharacterStyle*)), this, SLOT(addCharacterStyle(KoCharacterStyle*)));
281 disconnect(sm, SIGNAL(styleRemoved(KoParagraphStyle*)), this, SLOT(removeParagraphStyle(KoParagraphStyle*)));
282 disconnect(sm, SIGNAL(styleRemoved(KoCharacterStyle*)), this, SLOT(removeCharacterStyle(KoCharacterStyle*)));
283 }
284 m_styleManager = sm;
285 if (m_styleManager == 0) {
286 return;
287 }
288
289 if (m_modelType == StylesModel::ParagraphStyle) {
290 updateParagraphStyles();
291 connect(sm, SIGNAL(styleAdded(KoParagraphStyle*)), this, SLOT(addParagraphStyle(KoParagraphStyle*)));
292 connect(sm, SIGNAL(styleRemoved(KoParagraphStyle*)), this, SLOT(removeParagraphStyle(KoParagraphStyle*)));
293 } else {
294 updateCharacterStyles();
295 connect(sm, SIGNAL(styleAdded(KoCharacterStyle*)), this, SLOT(addCharacterStyle(KoCharacterStyle*)));
296 connect(sm, SIGNAL(styleRemoved(KoCharacterStyle*)), this, SLOT(removeCharacterStyle(KoCharacterStyle*)));
297 }
298 }
299
setStyleThumbnailer(KoStyleThumbnailer * thumbnailer)300 void StylesModel::setStyleThumbnailer(KoStyleThumbnailer *thumbnailer)
301 {
302 m_styleThumbnailer = thumbnailer;
303 }
304
305 // called when the stylemanager adds a style
addParagraphStyle(KoParagraphStyle * style)306 void StylesModel::addParagraphStyle(KoParagraphStyle *style)
307 {
308 Q_ASSERT(style);
309 QCollator collator;
310 QList<int>::iterator begin = m_styleList.begin();
311 int index = 0;
312 for (; begin != m_styleList.end(); ++begin) {
313 KoParagraphStyle *s = m_styleManager->paragraphStyle(*begin);
314 if (!s && m_draftParStyleList.contains(*begin)) {
315 s = m_draftParStyleList[*begin];
316 }
317 // s should be found as the manager and the m_styleList should be in sync
318 Q_ASSERT(s);
319 if (collator.compare(style->name(), s->name()) < 0) {
320 break;
321 }
322 ++index;
323 }
324 beginInsertRows(QModelIndex(), index, index);
325 m_styleList.insert(begin, style->styleId());
326 m_styleMapper->setMapping(style, style->styleId());
327 connect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map()));
328 endInsertRows();
329 }
330
sortParagraphStyleByName(KoParagraphStyle * style1,KoParagraphStyle * style2)331 bool sortParagraphStyleByName(KoParagraphStyle *style1, KoParagraphStyle *style2)
332 {
333 Q_ASSERT(style1);
334 Q_ASSERT(style2);
335 return QCollator().compare(style1->name(), style2->name()) < 0;
336 }
337
updateParagraphStyles()338 void StylesModel::updateParagraphStyles()
339 {
340 Q_ASSERT(m_styleManager);
341
342 beginResetModel();
343 m_styleList.clear();
344
345 QList<KoParagraphStyle *> styles = m_styleManager->paragraphStyles();
346 std::sort(styles.begin(), styles.end(), sortParagraphStyleByName);
347
348 Q_FOREACH (KoParagraphStyle *style, styles) {
349 if (style != m_styleManager->defaultParagraphStyle()) { //The default character style is not user selectable. It only provides individual property defaults and is not a style per say.
350 m_styleList.append(style->styleId());
351 m_styleMapper->setMapping(style, style->styleId());
352 connect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map()));
353 }
354 }
355
356 endResetModel();
357 }
358
359 // called when the stylemanager adds a style
addCharacterStyle(KoCharacterStyle * style)360 void StylesModel::addCharacterStyle(KoCharacterStyle *style)
361 {
362 Q_ASSERT(style);
363 // find the place where we need to insert the style
364 QCollator collator;
365 QList<int>::iterator begin = m_styleList.begin();
366 int index = 0;
367 // the None style should also be the first one so only start after it
368 if (begin != m_styleList.end() && *begin == NoneStyleId) {
369 ++begin;
370 ++index;
371 }
372 for (; begin != m_styleList.end(); ++begin) {
373 KoCharacterStyle *s = m_styleManager->characterStyle(*begin);
374 if (!s && m_draftCharStyleList.contains(*begin)) {
375 s = m_draftCharStyleList[*begin];
376 }
377 // s should be found as the manager and the m_styleList should be in sync
378 Q_ASSERT(s);
379 if (collator.compare(style->name(), s->name()) < 0) {
380 break;
381 }
382 ++index;
383 }
384 beginInsertRows(QModelIndex(), index, index);
385 m_styleList.insert(index, style->styleId());
386 endInsertRows();
387 m_styleMapper->setMapping(style, style->styleId());
388 connect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map()));
389 }
390
sortCharacterStyleByName(KoCharacterStyle * style1,KoCharacterStyle * style2)391 bool sortCharacterStyleByName(KoCharacterStyle *style1, KoCharacterStyle *style2)
392 {
393 Q_ASSERT(style1);
394 Q_ASSERT(style2);
395 return QCollator().compare(style1->name(), style2->name()) < 0;
396 }
397
updateCharacterStyles()398 void StylesModel::updateCharacterStyles()
399 {
400 Q_ASSERT(m_styleManager);
401
402 beginResetModel();
403 m_styleList.clear();
404
405 if (m_provideStyleNone && m_styleManager->paragraphStyles().count()) {
406 m_styleList.append(NoneStyleId);
407 }
408
409 QList<KoCharacterStyle *> styles = m_styleManager->characterStyles();
410 std::sort(styles.begin(), styles.end(), sortCharacterStyleByName);
411
412 Q_FOREACH (KoCharacterStyle *style, styles) {
413 if (style != m_styleManager->defaultCharacterStyle()) { //The default character style is not user selectable. It only provides individual property defaults and is not a style per say.
414 m_styleList.append(style->styleId());
415 m_styleMapper->setMapping(style, style->styleId());
416 connect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map()));
417 }
418 }
419
420 endResetModel();
421 }
422
423 // called when the stylemanager removes a style
removeParagraphStyle(KoParagraphStyle * style)424 void StylesModel::removeParagraphStyle(KoParagraphStyle *style)
425 {
426 int row = m_styleList.indexOf(style->styleId());
427 beginRemoveRows(QModelIndex(), row, row);
428 m_styleMapper->removeMappings(style);
429 disconnect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map()));
430 m_styleList.removeAt(row);
431 endRemoveRows();
432 }
433
434 // called when the stylemanager removes a style
removeCharacterStyle(KoCharacterStyle * style)435 void StylesModel::removeCharacterStyle(KoCharacterStyle *style)
436 {
437 int row = m_styleList.indexOf(style->styleId());
438 beginRemoveRows(QModelIndex(), row, row);
439 m_styleMapper->removeMappings(style);
440 disconnect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map()));
441 m_styleList.removeAt(row);
442 endRemoveRows();
443 }
444
updateName(int styleId)445 void StylesModel::updateName(int styleId)
446 {
447 // updating the name of a style can mean that the style needs to be moved inside the list to keep the sort order.
448 QCollator collator;
449 int oldIndex = m_styleList.indexOf(styleId);
450 if (oldIndex >= 0) {
451 int newIndex = 0;
452 if (m_modelType == StylesModel::ParagraphStyle) {
453 KoParagraphStyle *paragStyle = m_styleManager->paragraphStyle(styleId);
454 if (!paragStyle && m_draftParStyleList.contains(styleId)) {
455 paragStyle = m_draftParStyleList.value(styleId);
456 }
457 if (paragStyle) {
458 m_styleThumbnailer->removeFromCache(paragStyle);
459
460 QList<int>::iterator begin = m_styleList.begin();
461 for (; begin != m_styleList.end(); ++begin) {
462 // don't test again the same style
463 if (*begin == styleId) {
464 continue;
465 }
466 KoParagraphStyle *s = m_styleManager->paragraphStyle(*begin);
467 if (!s && m_draftParStyleList.contains(*begin)) {
468 s = m_draftParStyleList[*begin];
469 }
470 // s should be found as the manager and the m_styleList should be in sync
471 Q_ASSERT(s);
472 if (collator.compare(paragStyle->name(), s->name()) < 0) {
473 break;
474 }
475 ++newIndex;
476 }
477 if (oldIndex != newIndex) {
478 // beginMoveRows needs the index where it would be placed when it is still in the old position
479 // so add one when newIndex > oldIndex
480 beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex > oldIndex ? newIndex + 1 : newIndex);
481 m_styleList.removeAt(oldIndex);
482 m_styleList.insert(newIndex, styleId);
483 endMoveRows();
484 }
485 }
486 } else {
487 KoCharacterStyle *characterStyle = m_styleManager->characterStyle(styleId);
488 if (!characterStyle && m_draftCharStyleList.contains(styleId)) {
489 characterStyle = m_draftCharStyleList[styleId];
490 }
491 if (characterStyle) {
492 m_styleThumbnailer->removeFromCache(characterStyle);
493
494 QList<int>::iterator begin = m_styleList.begin();
495 if (begin != m_styleList.end() && *begin == NoneStyleId) {
496 ++begin;
497 ++newIndex;
498 }
499 for (; begin != m_styleList.end(); ++begin) {
500 // don't test again the same style
501 if (*begin == styleId) {
502 continue;
503 }
504 KoCharacterStyle *s = m_styleManager->characterStyle(*begin);
505 if (!s && m_draftCharStyleList.contains(*begin)) {
506 s = m_draftCharStyleList[*begin];
507 }
508 // s should be found as the manager and the m_styleList should be in sync
509 Q_ASSERT(s);
510 if (collator.compare(characterStyle->name(), s->name()) < 0) {
511 break;
512 }
513 ++newIndex;
514 }
515 if (oldIndex != newIndex) {
516 // beginMoveRows needs the index where it would be placed when it is still in the old position
517 // so add one when newIndex > oldIndex
518 beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex > oldIndex ? newIndex + 1 : newIndex);
519 m_styleList.removeAt(oldIndex);
520 m_styleList.insert(newIndex, styleId);
521 endMoveRows();
522 }
523 }
524 }
525 }
526 }
527
firstStyleIndex()528 QModelIndex StylesModel::firstStyleIndex()
529 {
530 if (!m_styleList.count()) {
531 return QModelIndex();
532 }
533 return createIndex(m_styleList.indexOf(m_styleList.at(0)), 0, m_styleList.at(0));
534 }
535
StyleList()536 QList<int> StylesModel::StyleList()
537 {
538 return m_styleList;
539 }
540
draftParStyleList()541 QHash<int, KoParagraphStyle *> StylesModel::draftParStyleList()
542 {
543 return m_draftParStyleList;
544 }
545
draftCharStyleList()546 QHash<int, KoCharacterStyle *> StylesModel::draftCharStyleList()
547 {
548 return m_draftCharStyleList;
549 }
550
addDraftParagraphStyle(KoParagraphStyle * style)551 void StylesModel::addDraftParagraphStyle(KoParagraphStyle *style)
552 {
553 style->setStyleId(-(m_draftParStyleList.count() + 1));
554 m_draftParStyleList.insert(style->styleId(), style);
555 addParagraphStyle(style);
556 }
557
addDraftCharacterStyle(KoCharacterStyle * style)558 void StylesModel::addDraftCharacterStyle(KoCharacterStyle *style)
559 {
560 if (m_draftCharStyleList.count() == 0) { // we have a character style "m_defaultCharacterStyle" with style id NoneStyleId in style model.
561 style->setStyleId(-(m_draftCharStyleList.count() + 2));
562 } else {
563 style->setStyleId(-(m_draftCharStyleList.count() + 1));
564 }
565 m_draftCharStyleList.insert(style->styleId(), style);
566 addCharacterStyle(style);
567 }
568
clearDraftStyles()569 void StylesModel::clearDraftStyles()
570 {
571 Q_FOREACH (KoParagraphStyle *style, m_draftParStyleList.values()) {
572 removeParagraphStyle(style);
573 }
574 m_draftParStyleList.clear();
575 Q_FOREACH (KoCharacterStyle *style, m_draftCharStyleList.values()) {
576 removeCharacterStyle(style);
577 }
578 m_draftCharStyleList.clear();
579 }
580
stylesType() const581 StylesModel::Type StylesModel::stylesType() const
582 {
583 return m_modelType;
584 }
585