1 /* This file is part of the KDE project
2
3 Copyright 2007 Johannes Simon <johannes.simon@gmail.com>
4 Copyright 2009 Inge Wallin <ingwa@lysator.liu.se>
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
22 // Own
23 #include "Surface.h"
24
25 // Qt
26 #include <QPointF>
27 #include <QBrush>
28 #include <QPen>
29
30 // Calligra
31 #include <KoXmlReader.h>
32 #include <KoXmlWriter.h>
33 #include <KoXmlNS.h>
34 #include <KoOdfStylesReader.h>
35 #include <KoShapeLoadingContext.h>
36 #include <KoShapeSavingContext.h>
37 #include <KoOdfLoadingContext.h>
38 #include <KoStyleStack.h>
39 #include <KoOdfGraphicStyles.h>
40 #include <KoGenStyles.h>
41 #include <KoOdfWorkaround.h>
42
43 #include <KoImageData.h>
44 #include <KoUnit.h>
45
46 // KChart
47 #include <KChartCartesianCoordinatePlane>
48 #include <KChartBackgroundAttributes>
49 #include <KChartFrameAttributes>
50
51 // KoChart
52 #include "PlotArea.h"
53 #include "ChartDebug.h"
54
55
56 using namespace KoChart;
57
58 class Surface::Private
59 {
60 public:
61 Private(PlotArea *parent);
62 ~Private();
63
64 PlotArea *plotArea;
65
66 QPointF position;
67 int width;
68
69 QBrush brush;
70 QPen framePen;
71
72 KChart::CartesianCoordinatePlane *kdPlane;
73 };
74
Private(PlotArea * parent)75 Surface::Private::Private(PlotArea *parent)
76 : plotArea(parent)
77 {
78 }
79
~Private()80 Surface::Private::~Private()
81 {
82 }
83
84
85 // ================================================================
86
87
Surface(PlotArea * parent)88 Surface::Surface(PlotArea *parent)
89 : d(new Private(parent))
90 {
91 Q_ASSERT(parent);
92
93 // FIXME: Make this class capable of storing floor-specific
94 // attributes as well. Right now, it's really only used
95 // and designed to load and save the chart's wall.
96 d->kdPlane = d->plotArea->kdCartesianPlane();
97 Q_ASSERT(d->kdPlane);
98 }
99
~Surface()100 Surface::~Surface()
101 {
102 delete d;
103 }
104
105
position() const106 QPointF Surface::position() const
107 {
108 return d->position;
109 }
110
setPosition(const QPointF & position)111 void Surface::setPosition(const QPointF &position)
112 {
113 d->position = position;
114 }
115
width() const116 int Surface::width() const
117 {
118 return d->width;
119 }
120
setWidth(int width)121 void Surface::setWidth(int width)
122 {
123 d->width = width;
124 }
125
brush() const126 QBrush Surface::brush() const
127 {
128 return d->brush;
129 }
130
setBrush(const QBrush & brush)131 void Surface::setBrush(const QBrush &brush)
132 {
133 d->brush = brush;
134 }
135
framePen() const136 QPen Surface::framePen() const
137 {
138 return d->framePen;
139 }
140
setFramePen(const QPen & pen)141 void Surface::setFramePen(const QPen &pen)
142 {
143 d->framePen = pen;
144 }
145
loadOdf(const KoXmlElement & surfaceElement,KoShapeLoadingContext & context)146 bool Surface::loadOdf(const KoXmlElement &surfaceElement,
147 KoShapeLoadingContext &context)
148 {
149 // Get the current style stack and save it's state.
150 KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
151
152 bool brushLoaded = false;
153
154 if (surfaceElement.hasAttributeNS(KoXmlNS::chart, "style-name")) {
155 KChart::BackgroundAttributes backgroundAttributes = d->kdPlane->backgroundAttributes();
156 KChart::FrameAttributes frameAttributes = d->kdPlane->frameAttributes();
157
158 // Add the chart style to the style stack.
159 styleStack.clear();
160 context.odfLoadingContext().fillStyleStack(surfaceElement, KoXmlNS::chart, "style-name", "chart");
161
162 styleStack.setTypeProperties("graphic");
163
164 // If there is a "stroke" property, then get the stroke style
165 // and set the pen accordingly.
166 if (styleStack.hasProperty(KoXmlNS::draw, "stroke")) {
167 frameAttributes.setVisible(true);
168
169 QString stroke = styleStack.property(KoXmlNS::draw, "stroke");
170 QPen pen(Qt::NoPen);
171 if (stroke == "solid" || stroke == "dash")
172 pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke,
173 context.odfLoadingContext().stylesReader());
174
175 frameAttributes.setPen(pen);
176 }
177
178 // If there is a "fill" property, then get the fill style, and
179 // set the brush for the surface accordingly.
180 if (styleStack.hasProperty(KoXmlNS::draw, "fill")) {
181 backgroundAttributes.setVisible(true);
182
183 QBrush brush;
184 QString fill = styleStack.property(KoXmlNS::draw, "fill");
185 if (fill == "solid" || fill == "hatch") {
186 brushLoaded = true;
187 brush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, fill,
188 context.odfLoadingContext().stylesReader());
189 }
190 else if (fill == "gradient") {
191 brushLoaded = true;
192 brush = KoOdfGraphicStyles::loadOdfGradientStyle(styleStack, context.odfLoadingContext().stylesReader(), QSizeF(5.0, 60.0));
193 }
194 else if (fill == "bitmap") {
195 brushLoaded = true;
196 brush = loadOdfPatternStyle(styleStack, context.odfLoadingContext(), QSizeF(5.0, 60.0));
197 }
198
199 backgroundAttributes.setBrush(brush);
200 }
201
202 // Finally actually set the attributes.
203 d->kdPlane->setBackgroundAttributes(backgroundAttributes);
204 d->kdPlane->setFrameAttributes(frameAttributes);
205 }
206
207 #ifndef NWORKAROUND_ODF_BUGS
208 if (!brushLoaded) {
209 KChart::BackgroundAttributes backgroundAttributes = d->kdPlane->backgroundAttributes();
210 QColor fillColor = KoOdfWorkaround::fixMissingFillColor(surfaceElement, context);
211 if (fillColor.isValid()) {
212 backgroundAttributes.setVisible(true);
213 backgroundAttributes.setBrush(fillColor);
214 d->kdPlane->setBackgroundAttributes(backgroundAttributes);
215 }
216 }
217 #endif
218
219 return true;
220 }
221
saveOdf(KoShapeSavingContext & context,const char * elementName)222 void Surface::saveOdf(KoShapeSavingContext &context, const char *elementName)
223 {
224 KoXmlWriter &bodyWriter = context.xmlWriter();
225 KoGenStyles &mainStyles = context.mainStyles();
226 KoGenStyle style = KoGenStyle(KoGenStyle::GraphicAutoStyle, "chart");
227
228 // elementName is chart:floor or chart:wall
229 bodyWriter.startElement(elementName);
230
231 QBrush backgroundBrush;
232 if (d->kdPlane->backgroundAttributes().isVisible())
233 backgroundBrush = d->kdPlane->backgroundAttributes().brush();
234 QPen framePen(Qt::NoPen);
235 if (d->kdPlane->frameAttributes().isVisible())
236 framePen = d->kdPlane->frameAttributes().pen();
237
238 KoOdfGraphicStyles::saveOdfFillStyle(style, mainStyles, backgroundBrush);
239 KoOdfGraphicStyles::saveOdfStrokeStyle(style, mainStyles, framePen);
240
241 bodyWriter.addAttribute("chart:style-name", mainStyles.insert(style, "ch"));
242
243 bodyWriter.endElement(); // chart:floor or chart:wall
244 }
245
loadOdfPatternStyle(const KoStyleStack & styleStack,KoOdfLoadingContext & context,const QSizeF & size)246 QBrush Surface::loadOdfPatternStyle(const KoStyleStack &styleStack,
247 KoOdfLoadingContext &context, const QSizeF &size)
248 {
249 QString styleName = styleStack.property(KoXmlNS::draw, "fill-image-name");
250
251 KoXmlElement* e = context.stylesReader().drawStyles("fill-image")[styleName];
252 if (! e)
253 return QBrush();
254
255 const QString href = e->attributeNS(KoXmlNS::xlink, "href", QString());
256
257 if (href.isEmpty())
258 return QBrush();
259
260 QString strExtension;
261 const int result = href.lastIndexOf('.');
262 if (result >= 0) {
263 strExtension = href.mid(result + 1); // As we are using KoPicture, the extension should be without the dot.
264 }
265 QString filename(href);
266
267 KoImageData data;
268 data.setImage(href, context.store());
269 if (data.errorCode() != KoImageData::Success)
270 return QBrush();
271
272 // read the pattern repeat style
273 QString style = styleStack.property(KoXmlNS::style, "repeat");
274 debugChart << "pattern style =" << style;
275
276 QSize imageSize = data.image().size();
277
278 if (style == "stretch") {
279 imageSize = size.toSize();
280 } else {
281 // optional attributes which can override original image size
282 if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-height") && styleStack.hasProperty(KoXmlNS::draw, "fill-image-width")) {
283 QString height = styleStack.property(KoXmlNS::draw, "fill-image-height");
284 qreal newHeight = 0.0;
285 if (height.endsWith('%'))
286 newHeight = 0.01 * height.remove('%').toDouble() * imageSize.height();
287 else
288 newHeight = KoUnit::parseValue(height);
289 QString width = styleStack.property(KoXmlNS::draw, "fill-image-width");
290 qreal newWidth = 0.0;
291 if (width.endsWith('%'))
292 newWidth = 0.01 * width.remove('%').toDouble() * imageSize.width();
293 else
294 newWidth = KoUnit::parseValue(width);
295 if (newHeight > 0.0)
296 imageSize.setHeight(static_cast<int>(newHeight));
297 if (newWidth > 0.0)
298 imageSize.setWidth(static_cast<int>(newWidth));
299 }
300 }
301
302 debugChart << "shape size =" << size;
303 debugChart << "original image size =" << data.image().size();
304 debugChart << "resulting image size =" << imageSize;
305
306 QBrush resultBrush(QPixmap::fromImage(data.image()).scaled(imageSize));
307
308 if (style == "repeat") {
309 QTransform matrix;
310 if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point")) {
311 // align pattern to the given size
312 QString align = styleStack.property(KoXmlNS::draw, "fill-image-ref-point");
313 debugChart << "pattern align =" << align;
314 if (align == "top-left")
315 matrix.translate(0, 0);
316 else if (align == "top")
317 matrix.translate(0.5*size.width(), 0);
318 else if (align == "top-right")
319 matrix.translate(size.width(), 0);
320 else if (align == "left")
321 matrix.translate(0, 0.5*size.height());
322 else if (align == "center")
323 matrix.translate(0.5*size.width(), 0.5*size.height());
324 else if (align == "right")
325 matrix.translate(size.width(), 0.5*size.height());
326 else if (align == "bottom-left")
327 matrix.translate(0, size.height());
328 else if (align == "bottom")
329 matrix.translate(0.5*size.width(), size.height());
330 else if (align == "bottom-right")
331 matrix.translate(size.width(), size.height());
332 }
333 if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-x")) {
334 QString pointX = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-x");
335 matrix.translate(0.01 * pointX.remove('%').toDouble() * imageSize.width(), 0);
336 }
337 if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-y")) {
338 QString pointY = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-y");
339 matrix.translate(0, 0.01 * pointY.remove('%').toDouble() * imageSize.height());
340 }
341 resultBrush.setTransform(matrix);
342 }
343
344 return resultBrush;
345 }
346