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 &sectionFormat, 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 &section) const
158 {
159     QTextFrameFormat format = section.frameFormat();
160     applyStyle(format);
161     section.setFrameFormat(format);
162 }
163 
unapplyStyle(QTextFrame & section) const164 void KoSectionStyle::unapplyStyle(QTextFrame &section) 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