1 /* This file is part of the KDE project
2 * Copyright (C) 2006-2009 Thomas Zander <zander@kde.org>
3 * Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
4 * Copyright (C) 2008 Roopesh Chander <roop@forwardbias.in>
5 * Copyright (C) 2008 Girish Ramakrishnan <girish@forwardbias.in>
6 * Copyright (C) 2009 KO GmbH <cbo@kogmbh.com>
7 * Copyright 2012 Friedrich W. H. Kossebau <kossebau@kde.org>
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB. If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 */
24 #include "KoSectionStyle.h"
25
26 #include <KoGenStyle.h>
27 #include "Styles_p.h"
28
29 #include <QTextFrame>
30 #include <QTextFrameFormat>
31 #include <QBuffer>
32
33 #include <KoColumns.h>
34 #include <KoUnit.h>
35 #include <KoStyleStack.h>
36 #include <KoOdfLoadingContext.h>
37 #include <KoXmlNS.h>
38 #include <KoXmlWriter.h>
39 #include <KoXmlReader.h>
40
41 #include "TextDebug.h"
42
43
44 Q_DECLARE_METATYPE(QList<KoColumns::ColumnDatum>)
45
46 class Q_DECL_HIDDEN KoSectionStyle::Private
47 {
48 public:
Private()49 Private() : parentStyle(0) {}
50
~Private()51 ~Private() {
52 }
53
setProperty(int key,const QVariant & value)54 void setProperty(int key, const QVariant &value) {
55 stylesPrivate.add(key, value);
56 }
propertyInt(int key) const57 int propertyInt(int key) const {
58 QVariant variant = stylesPrivate.value(key);
59 if (variant.isNull())
60 return 0;
61 return variant.toInt();
62 }
propertyBoolean(int key) const63 bool propertyBoolean(int key) const {
64 QVariant variant = stylesPrivate.value(key);
65 if (variant.isNull())
66 return false;
67 return variant.toBool();
68 }
propertyDouble(int key) const69 qreal propertyDouble(int key) const {
70 QVariant variant = stylesPrivate.value(key);
71 if (variant.isNull())
72 return 0.0;
73 return variant.toDouble();
74 }
propertyColor(int key) const75 QColor propertyColor(int key) const {
76 QVariant variant = stylesPrivate.value(key);
77 if (variant.isNull())
78 return QColor();
79 return variant.value<QColor>();
80 }
propertyColumnData() const81 QList<KoColumns::ColumnDatum> propertyColumnData() const{
82 QVariant variant = stylesPrivate.value(ColumnData);
83 if (variant.isNull())
84 return QList<KoColumns::ColumnDatum>();
85 return variant.value<QList<KoColumns::ColumnDatum> >();
86 }
87
88 QString name;
89 KoSectionStyle *parentStyle;
90 StylePrivate stylesPrivate;
91 };
92
KoSectionStyle(QObject * parent)93 KoSectionStyle::KoSectionStyle(QObject *parent)
94 : QObject(parent), d(new Private())
95 {
96 }
97
KoSectionStyle(const QTextFrameFormat & sectionFormat,QObject * parent)98 KoSectionStyle::KoSectionStyle(const QTextFrameFormat §ionFormat, QObject *parent)
99 : QObject(parent),
100 d(new Private())
101 {
102 d->stylesPrivate = sectionFormat.properties();
103 }
104
~KoSectionStyle()105 KoSectionStyle::~KoSectionStyle()
106 {
107 delete d;
108 }
109
setParentStyle(KoSectionStyle * parent)110 void KoSectionStyle::setParentStyle(KoSectionStyle *parent)
111 {
112 d->parentStyle = parent;
113 }
114
setProperty(int key,const QVariant & value)115 void KoSectionStyle::setProperty(int key, const QVariant &value)
116 {
117 if (d->parentStyle) {
118 QVariant var = d->parentStyle->value(key);
119 if (!var.isNull() && var == value) { // same as parent, so its actually a reset.
120 d->stylesPrivate.remove(key);
121 return;
122 }
123 }
124 d->stylesPrivate.add(key, value);
125 }
126
remove(int key)127 void KoSectionStyle::remove(int key)
128 {
129 d->stylesPrivate.remove(key);
130 }
131
value(int key) const132 QVariant KoSectionStyle::value(int key) const
133 {
134 QVariant var = d->stylesPrivate.value(key);
135 if (var.isNull() && d->parentStyle)
136 var = d->parentStyle->value(key);
137 return var;
138 }
139
hasProperty(int key) const140 bool KoSectionStyle::hasProperty(int key) const
141 {
142 return d->stylesPrivate.contains(key);
143 }
144
applyStyle(QTextFrameFormat & format) const145 void KoSectionStyle::applyStyle(QTextFrameFormat &format) const
146 {
147 if (d->parentStyle) {
148 d->parentStyle->applyStyle(format);
149 }
150 QList<int> keys = d->stylesPrivate.keys();
151 for (int i = 0; i < keys.count(); i++) {
152 QVariant variant = d->stylesPrivate.value(keys[i]);
153 format.setProperty(keys[i], variant);
154 }
155 }
156
applyStyle(QTextFrame & section) const157 void KoSectionStyle::applyStyle(QTextFrame §ion) const
158 {
159 QTextFrameFormat format = section.frameFormat();
160 applyStyle(format);
161 section.setFrameFormat(format);
162 }
163
unapplyStyle(QTextFrame & section) const164 void KoSectionStyle::unapplyStyle(QTextFrame §ion) const
165 {
166 if (d->parentStyle)
167 d->parentStyle->unapplyStyle(section);
168
169 QTextFrameFormat format = section.frameFormat();
170
171 QList<int> keys = d->stylesPrivate.keys();
172 for (int i = 0; i < keys.count(); i++) {
173 QVariant variant = d->stylesPrivate.value(keys[i]);
174 if (variant == format.property(keys[i]))
175 format.clearProperty(keys[i]);
176 }
177 section.setFrameFormat(format);
178 }
179
setLeftMargin(qreal margin)180 void KoSectionStyle::setLeftMargin(qreal margin)
181 {
182 setProperty(QTextFormat::BlockLeftMargin, margin);
183 }
184
leftMargin() const185 qreal KoSectionStyle::leftMargin() const
186 {
187 return d->propertyDouble(QTextFormat::BlockLeftMargin);
188 }
189
setRightMargin(qreal margin)190 void KoSectionStyle::setRightMargin(qreal margin)
191 {
192 setProperty(QTextFormat::BlockRightMargin, margin);
193 }
194
rightMargin() const195 qreal KoSectionStyle::rightMargin() const
196 {
197 return d->propertyDouble(QTextFormat::BlockRightMargin);
198 }
199
setColumnCount(int columnCount)200 void KoSectionStyle::setColumnCount(int columnCount)
201 {
202 setProperty(ColumnCount, columnCount);
203 }
204
columnCount() const205 int KoSectionStyle::columnCount() const
206 {
207 return d->propertyInt(ColumnCount);
208 }
209
setColumnGapWidth(qreal columnGapWidth)210 void KoSectionStyle::setColumnGapWidth(qreal columnGapWidth)
211 {
212 setProperty(ColumnGapWidth, columnGapWidth);
213 }
214
columnGapWidth() const215 qreal KoSectionStyle::columnGapWidth() const
216 {
217 return d->propertyDouble(ColumnGapWidth);
218 }
219
setColumnData(const QList<KoColumns::ColumnDatum> & columnData)220 void KoSectionStyle::setColumnData(const QList<KoColumns::ColumnDatum> &columnData)
221 {
222 setProperty(ColumnData, QVariant::fromValue<QList<KoColumns::ColumnDatum> >(columnData));
223 }
224
columnData() const225 QList<KoColumns::ColumnDatum> KoSectionStyle::columnData() const
226 {
227 return d->propertyColumnData();
228 }
229
setSeparatorStyle(KoColumns::SeparatorStyle separatorStyle)230 void KoSectionStyle::setSeparatorStyle(KoColumns::SeparatorStyle separatorStyle)
231 {
232 setProperty(SeparatorStyle, separatorStyle);
233 }
234
separatorStyle() const235 KoColumns::SeparatorStyle KoSectionStyle::separatorStyle() const
236 {
237 return static_cast<KoColumns::SeparatorStyle>(d->propertyInt(SeparatorStyle));
238 }
239
setSeparatorColor(const QColor & separatorColor)240 void KoSectionStyle::setSeparatorColor(const QColor &separatorColor)
241 {
242 setProperty(SeparatorColor, separatorColor);
243 }
244
separatorColor() const245 QColor KoSectionStyle::separatorColor() const
246 {
247 return d->propertyColor(SeparatorColor);
248 }
249
setSeparatorWidth(qreal separatorWidth)250 void KoSectionStyle::setSeparatorWidth(qreal separatorWidth)
251 {
252 setProperty(SeparatorWidth, separatorWidth);
253 }
254
separatorWidth() const255 qreal KoSectionStyle::separatorWidth() const
256 {
257 return d->propertyDouble(SeparatorWidth);
258 }
259
setSeparatorHeight(int separatorHeight)260 void KoSectionStyle::setSeparatorHeight( int separatorHeight)
261 {
262 setProperty(SeparatorHeight, separatorHeight);
263 }
264
separatorHeight() const265 int KoSectionStyle::separatorHeight() const
266 {
267 return d->propertyInt(SeparatorHeight);
268 }
269
setSeparatorVerticalAlignment(KoColumns::SeparatorVerticalAlignment separatorVerticalAlignment)270 void KoSectionStyle::setSeparatorVerticalAlignment(KoColumns::SeparatorVerticalAlignment separatorVerticalAlignment)
271 {
272 setProperty(SeparatorVerticalAlignment, separatorVerticalAlignment);
273 }
274
separatorVerticalAlignment() const275 KoColumns::SeparatorVerticalAlignment KoSectionStyle::separatorVerticalAlignment() const
276 {
277 return static_cast<KoColumns::SeparatorVerticalAlignment>(d->propertyInt(SeparatorVerticalAlignment));
278 }
279
280
parentStyle() const281 KoSectionStyle *KoSectionStyle::parentStyle() const
282 {
283 return d->parentStyle;
284 }
285
name() const286 QString KoSectionStyle::name() const
287 {
288 return d->name;
289 }
290
setName(const QString & name)291 void KoSectionStyle::setName(const QString &name)
292 {
293 if (name == d->name)
294 return;
295 d->name = name;
296 emit nameChanged(name);
297 }
298
styleId() const299 int KoSectionStyle::styleId() const
300 {
301 return d->propertyInt(StyleId);
302 }
303
setStyleId(int id)304 void KoSectionStyle::setStyleId(int id)
305 {
306 setProperty(StyleId, id);
307 }
308
309
textProgressionDirection() const310 KoText::Direction KoSectionStyle::textProgressionDirection() const
311 {
312 return static_cast<KoText::Direction>(d->propertyInt(TextProgressionDirection));
313 }
314
setTextProgressionDirection(KoText::Direction dir)315 void KoSectionStyle::setTextProgressionDirection(KoText::Direction dir)
316 {
317 setProperty(TextProgressionDirection, dir);
318 }
319
setBackground(const QBrush & brush)320 void KoSectionStyle::setBackground(const QBrush &brush)
321 {
322 d->setProperty(QTextFormat::BackgroundBrush, brush);
323 }
324
clearBackground()325 void KoSectionStyle::clearBackground()
326 {
327 d->stylesPrivate.remove(QTextCharFormat::BackgroundBrush);
328 }
329
background() const330 QBrush KoSectionStyle::background() const
331 {
332 QVariant variant = d->stylesPrivate.value(QTextFormat::BackgroundBrush);
333
334 if (variant.isNull()) {
335 QBrush brush;
336 return brush;
337 }
338 return qvariant_cast<QBrush>(variant);
339 }
340
loadOdf(const KoXmlElement * element,KoOdfLoadingContext & context)341 void KoSectionStyle::loadOdf(const KoXmlElement *element, KoOdfLoadingContext &context)
342 {
343 if (element->hasAttributeNS(KoXmlNS::style, "display-name"))
344 d->name = element->attributeNS(KoXmlNS::style, "display-name", QString());
345
346 if (d->name.isEmpty()) // if no style:display-name is given us the style:name
347 d->name = element->attributeNS(KoXmlNS::style, "name", QString());
348
349 context.styleStack().save();
350 // Load all parents - only because we don't support inheritance.
351 QString family = element->attributeNS(KoXmlNS::style, "family", "section");
352 context.addStyles(element, family.toLocal8Bit().constData()); // Load all parents - only because we don't support inheritance.
353
354 context.styleStack().setTypeProperties("section"); // load all style attributes from "style:section-properties"
355
356 KoStyleStack &styleStack = context.styleStack();
357
358 // in 1.6 this was defined at KoParagLayout::loadOasisParagLayout(KoParagLayout&, KoOasisContext&)
359
360 if (styleStack.hasProperty(KoXmlNS::style, "writing-mode")) { // http://www.w3.org/TR/2004/WD-xsl11-20041216/#writing-mode
361 QString writingMode = styleStack.property(KoXmlNS::style, "writing-mode");
362 setTextProgressionDirection(KoText::directionFromString(writingMode));
363 }
364
365 // Indentation (margin)
366 bool hasMarginLeft = styleStack.hasProperty(KoXmlNS::fo, "margin-left");
367 bool hasMarginRight = styleStack.hasProperty(KoXmlNS::fo, "margin-right");
368 if (hasMarginLeft)
369 setLeftMargin(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "margin-left")));
370 if (hasMarginRight)
371 setRightMargin(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "margin-right")));
372
373
374 // The fo:background-color attribute specifies the background color of a paragraph.
375 if (styleStack.hasProperty(KoXmlNS::fo, "background-color")) {
376 const QString bgcolor = styleStack.property(KoXmlNS::fo, "background-color");
377 QBrush brush = background();
378 if (bgcolor == "transparent")
379 brush.setStyle(Qt::NoBrush);
380 else {
381 if (brush.style() == Qt::NoBrush)
382 brush.setStyle(Qt::SolidPattern);
383 brush.setColor(bgcolor); // #rrggbb format
384 }
385 setBackground(brush);
386 }
387
388 if (styleStack.hasChildNode(KoXmlNS::style, "columns")) {
389 KoXmlElement columns = styleStack.childNode(KoXmlNS::style, "columns");
390 int columnCount = columns.attributeNS(KoXmlNS::fo, "column-count").toInt();
391 if (columnCount < 1)
392 columnCount = 1;
393 setColumnCount(columnCount);
394
395 if (styleStack.hasProperty(KoXmlNS::fo, "column-gap")) {
396 setColumnGapWidth(KoUnit::parseValue(columns.attributeNS(KoXmlNS::fo, "column-gap")));
397 } else {
398 QList <KoColumns::ColumnDatum> columnData;
399
400 KoXmlElement columnElement;
401 forEachElement(columnElement, columns) {
402 if(columnElement.localName() != QLatin1String("column") ||
403 columnElement.namespaceURI() != KoXmlNS::style)
404 continue;
405
406 KoColumns::ColumnDatum datum;
407 datum.leftMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "start-indent"), 0.0);
408 datum.rightMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "end-indent"), 0.0);
409 datum.topMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "space-before"), 0.0);
410 datum.bottomMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "space-after"), 0.0);
411 datum.relativeWidth = KoColumns::parseRelativeWidth(columnElement.attributeNS(KoXmlNS::style, "rel-width"));
412 // on a bad relativeWidth just drop all data
413 if (datum.relativeWidth <= 0) {
414 columnData.clear();
415 break;
416 }
417
418 columnData.append(datum);
419 }
420
421 if (! columnData.isEmpty()) {
422 setColumnData(columnData);
423 }
424 }
425
426 KoXmlElement columnSep = KoXml::namedItemNS(columns, KoXmlNS::style, "column-sep");
427 if (! columnSep.isNull()) {
428 if (columnSep.hasAttributeNS(KoXmlNS::style, "style"))
429 setSeparatorStyle(KoColumns::parseSeparatorStyle(columnSep.attributeNS(KoXmlNS::style, "style")));
430 if (columnSep.hasAttributeNS(KoXmlNS::style, "width"))
431 setSeparatorWidth(KoUnit::parseValue(columnSep.attributeNS(KoXmlNS::style, "width")));
432 if (columnSep.hasAttributeNS(KoXmlNS::style, "height"))
433 setSeparatorHeight(KoColumns::parseSeparatorHeight(columnSep.attributeNS(KoXmlNS::style, "height")));
434 if (columnSep.hasAttributeNS(KoXmlNS::style, "color"))
435 setSeparatorColor(KoColumns::parseSeparatorColor(columnSep.attributeNS(KoXmlNS::style, "color")));
436 if (columnSep.hasAttributeNS(KoXmlNS::style, "vertical-align"))
437 setSeparatorVerticalAlignment(
438 KoColumns::parseSeparatorVerticalAlignment(columnSep.attributeNS(KoXmlNS::style, "vertical-align")));
439 }
440 }
441
442 styleStack.restore();
443 }
444
445
copyProperties(const KoSectionStyle * style)446 void KoSectionStyle::copyProperties(const KoSectionStyle *style)
447 {
448 d->stylesPrivate = style->d->stylesPrivate;
449 setName(style->name()); // make sure we emit property change
450 d->parentStyle = style->d->parentStyle;
451 }
452
clone(QObject * parent) const453 KoSectionStyle *KoSectionStyle::clone(QObject *parent) const
454 {
455 KoSectionStyle *newStyle = new KoSectionStyle(parent);
456 newStyle->copyProperties(this);
457 return newStyle;
458 }
459
operator ==(const KoSectionStyle & other) const460 bool KoSectionStyle::operator==(const KoSectionStyle &other) const
461 {
462 return other.d->stylesPrivate == d->stylesPrivate;
463 }
464
removeDuplicates(const KoSectionStyle & other)465 void KoSectionStyle::removeDuplicates(const KoSectionStyle &other)
466 {
467 d->stylesPrivate.removeDuplicates(other.d->stylesPrivate);
468 }
469
470
saveOdf(KoGenStyle & style)471 void KoSectionStyle::saveOdf(KoGenStyle &style)
472 {
473 // only custom style have a displayname. automatic styles don't have a name set.
474 if (!d->name.isEmpty() && !style.isDefaultStyle()) {
475 style.addAttribute("style:display-name", d->name);
476 }
477
478 QList<int> columnsKeys;
479
480 QList<int> keys = d->stylesPrivate.keys();
481 Q_FOREACH (int key, keys) {
482 switch (key) {
483 case KoSectionStyle::TextProgressionDirection: {
484 int directionValue = 0;
485 bool ok = false;
486 directionValue = d->stylesPrivate.value(key).toInt(&ok);
487 if (ok) {
488 QString direction;
489 if (directionValue == KoText::LeftRightTopBottom)
490 direction = "lr-tb";
491 else if (directionValue == KoText::RightLeftTopBottom)
492 direction = "rl-tb";
493 else if (directionValue == KoText::TopBottomRightLeft)
494 direction = "tb-lr";
495 else if (directionValue == KoText::InheritDirection)
496 direction = "page";
497 if (!direction.isEmpty())
498 style.addProperty("style:writing-mode", direction, KoGenStyle::DefaultType);
499 }
500 break;
501 }
502 case QTextFormat::BackgroundBrush: {
503 QBrush backBrush = background();
504 if (backBrush.style() != Qt::NoBrush)
505 style.addProperty("fo:background-color", backBrush.color().name(), KoGenStyle::ParagraphType);
506 else
507 style.addProperty("fo:background-color", "transparent", KoGenStyle::DefaultType);
508 break;
509 }
510 case QTextFormat::BlockLeftMargin:
511 style.addPropertyPt("fo:margin-left", leftMargin(), KoGenStyle::DefaultType);
512 break;
513 case QTextFormat::BlockRightMargin:
514 style.addPropertyPt("fo:margin-right", rightMargin(), KoGenStyle::DefaultType);
515 break;
516 case ColumnCount:
517 case ColumnGapWidth:
518 case SeparatorStyle:
519 case SeparatorColor:
520 case SeparatorVerticalAlignment:
521 case SeparatorWidth:
522 case SeparatorHeight:
523 columnsKeys.append(key);
524 break;
525 }
526 }
527
528 if (!columnsKeys.isEmpty()) {
529 QBuffer buffer;
530 buffer.open(QIODevice::WriteOnly);
531 KoXmlWriter elementWriter(&buffer); // TODO pass indentation level
532
533 elementWriter.startElement("style:columns");
534 // seems these two are mandatory
535 elementWriter.addAttribute("fo:column-count", columnCount());
536 elementWriter.addAttribute("fo:column-gap", columnGapWidth());
537 columnsKeys.removeOne(ColumnCount);
538 columnsKeys.removeOne(ColumnGapWidth);
539
540 if (!columnsKeys.isEmpty()) {
541 elementWriter.startElement("style:column-sep");
542 Q_FOREACH (int key, columnsKeys) {
543 switch (key) {
544 case SeparatorStyle:
545 elementWriter.addAttribute("style:style",
546 KoColumns::separatorStyleString(separatorStyle()));
547 break;
548 case SeparatorColor:
549 elementWriter.addAttribute("style:color",
550 separatorColor().name());
551 break;
552 case SeparatorVerticalAlignment:
553 elementWriter.addAttribute("style:vertical-align",
554 KoColumns::separatorVerticalAlignmentString(separatorVerticalAlignment()));
555 break;
556 case SeparatorWidth:
557 elementWriter.addAttribute("style:width",
558 separatorWidth());
559 break;
560 case SeparatorHeight:
561 elementWriter.addAttribute("style:height",
562 QString::fromLatin1("%1%").arg(separatorHeight()));
563 break;
564 }
565 }
566 elementWriter.endElement(); // style:column-sep
567 }
568
569 elementWriter.endElement(); // style:columns
570 const QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
571 style.addChildElement("style:columns", elementContents);
572 }
573 }
574