File indexing completed on 2024-05-12 16:33:33
0001 /* This file is part of the KDE project 0002 0003 Copyright 2007 Johannes Simon <johannes.simon@gmail.com> 0004 Copyright 2010 Inge Wallin <inge@lysator.liu.se> 0005 0006 This library is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU Library General Public 0008 License as published by the Free Software Foundation; either 0009 version 2 of the License, or (at your option) any later version. 0010 0011 This library is distributed in the hope that it will be useful, 0012 but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 Library General Public License for more details. 0015 0016 You should have received a copy of the GNU Library General Public License 0017 along with this library; see the file COPYING.LIB. If not, write to 0018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0019 Boston, MA 02110-1301, USA. 0020 */ 0021 0022 // Own 0023 #include "Legend.h" 0024 0025 // Qt 0026 #include <QString> 0027 #include <QSizeF> 0028 #include <QPen> 0029 #include <QColor> 0030 #include <QBrush> 0031 #include <QFont> 0032 #include <QImage> 0033 #include <QPainterPath> 0034 0035 // Calligra 0036 #include <KoXmlReader.h> 0037 #include <KoXmlWriter.h> 0038 #include <KoShapeLoadingContext.h> 0039 #include <KoShapeSavingContext.h> 0040 #include <KoOdfLoadingContext.h> 0041 #include <KoXmlNS.h> 0042 #include <KoGenStyles.h> 0043 #include <KoStyleStack.h> 0044 #include <KoUnit.h> 0045 #include <KoColorBackground.h> 0046 #include <KoShapeStroke.h> 0047 0048 // KChart 0049 #include <KChartChart> 0050 #include <KChartBarDiagram> 0051 #include <KChartAbstractDiagram> 0052 #include <KChartFrameAttributes> 0053 #include <KChartBackgroundAttributes> 0054 #include <KChartLegend> 0055 #include "KChartConvertions.h" 0056 #include "kchart_version.h" 0057 0058 // KoChart 0059 #include "PlotArea.h" 0060 #include "ScreenConversions.h" 0061 #include "ChartLayout.h" 0062 #include "OdfLoadingHelper.h" 0063 #include "OdfHelper.h" 0064 0065 using namespace KoChart; 0066 0067 class Legend::Private { 0068 public: 0069 Private(); 0070 ~Private(); 0071 0072 ChartShape *shape; 0073 0074 // Properties of the Legend 0075 QString title; 0076 LegendExpansion expansion; 0077 Position position; 0078 QFont font; 0079 QFont titleFont; 0080 QColor fontColor; 0081 Qt::Alignment alignment; 0082 KoShapeStroke *lineBorder; 0083 0084 // The connection to KChart 0085 KChart::Legend *kdLegend; 0086 0087 QImage image; 0088 0089 mutable bool pixmapRepaintRequested; 0090 QSizeF lastSize; 0091 QPointF lastZoomLevel; 0092 }; 0093 0094 0095 Legend::Private::Private() 0096 { 0097 lineBorder = new KoShapeStroke(0.5, Qt::black); 0098 expansion = HighLegendExpansion; 0099 alignment = Qt::AlignCenter; 0100 pixmapRepaintRequested = true; 0101 position = EndPosition; 0102 } 0103 0104 Legend::Private::~Private() 0105 { 0106 delete lineBorder; 0107 } 0108 0109 0110 Legend::Legend(ChartShape *parent) 0111 : QObject(parent) 0112 , d(new Private()) 0113 { 0114 Q_ASSERT(parent); 0115 0116 setShapeId("ChartShapeLegend"); 0117 0118 d->shape = parent; 0119 0120 d->kdLegend = new KChart::Legend(); 0121 d->kdLegend->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); 0122 0123 // we use the shape to display frame and background 0124 KChart::FrameAttributes frameAttr = d->kdLegend->frameAttributes(); 0125 frameAttr.setVisible(false); 0126 d->kdLegend->setFrameAttributes(frameAttr); 0127 0128 setTitleFontSize(10); 0129 setTitle(QString()); 0130 setFontSize(8); 0131 0132 update(); 0133 0134 parent->addShape(this); 0135 0136 setAllowedInteraction(KoShape::ResizeAllowed, false); 0137 setAllowedInteraction(KoShape::RotationAllowed, false); 0138 0139 connect (d->kdLegend, SIGNAL(propertiesChanged()), 0140 this, SLOT(slotKdLegendChanged())); 0141 connect (parent, SIGNAL(chartTypeChanged(ChartType, ChartType)), 0142 this, SLOT(slotChartTypeChanged(ChartType))); 0143 } 0144 0145 Legend::~Legend() 0146 { 0147 delete d->kdLegend; 0148 delete d; 0149 } 0150 0151 0152 QString Legend::title() const 0153 { 0154 return d->title; 0155 } 0156 0157 void Legend::setTitle(const QString &title) 0158 { 0159 d->title = title; 0160 d->kdLegend->setTitleText(title); 0161 d->pixmapRepaintRequested = true; 0162 0163 emit updateConfigWidget(); 0164 } 0165 0166 QFont Legend::font() const 0167 { 0168 return d->font; 0169 } 0170 0171 void Legend::setFont(const QFont &font) 0172 { 0173 d->font = font; 0174 0175 // KChart 0176 KChart::TextAttributes attributes = d->kdLegend->textAttributes(); 0177 attributes.setFont(font); 0178 d->kdLegend->setTextAttributes(attributes); 0179 0180 d->pixmapRepaintRequested = true; 0181 emit updateConfigWidget(); 0182 } 0183 0184 qreal Legend::fontSize() const 0185 { 0186 return d->font.pointSizeF(); 0187 } 0188 0189 void Legend::setFontSize(qreal size) 0190 { 0191 d->font.setPointSizeF(size); 0192 0193 // KChart 0194 KChart::TextAttributes attributes = d->kdLegend->textAttributes(); 0195 KChart::Measure m = attributes.fontSize(); 0196 m.setValue(size); 0197 attributes.setFontSize(m); 0198 d->kdLegend->setTextAttributes(attributes); 0199 0200 d->pixmapRepaintRequested = true; 0201 emit updateConfigWidget(); 0202 } 0203 0204 void Legend::setFontColor(const QColor &color) 0205 { 0206 KChart::TextAttributes attributes = d->kdLegend->textAttributes(); 0207 QPen pen = attributes.pen(); 0208 pen.setColor(color); 0209 attributes.setPen(pen); 0210 d->kdLegend->setTextAttributes(attributes); 0211 0212 d->pixmapRepaintRequested = true; 0213 } 0214 0215 QColor Legend::fontColor() const 0216 { 0217 KChart::TextAttributes attributes = d->kdLegend->textAttributes(); 0218 QPen pen = attributes.pen(); 0219 return pen.color(); 0220 } 0221 0222 QFont Legend::titleFont() const 0223 { 0224 return d->titleFont; 0225 } 0226 0227 void Legend::setTitleFont(const QFont &font) 0228 { 0229 d->titleFont = font; 0230 0231 // KChart 0232 KChart::TextAttributes attributes = d->kdLegend->titleTextAttributes(); 0233 attributes.setFont(font); 0234 d->kdLegend->setTitleTextAttributes(attributes); 0235 0236 d->pixmapRepaintRequested = true; 0237 } 0238 0239 qreal Legend::titleFontSize() const 0240 { 0241 return d->titleFont.pointSizeF(); 0242 } 0243 0244 void Legend::setTitleFontSize(qreal size) 0245 { 0246 d->titleFont.setPointSizeF(size); 0247 0248 // KChart 0249 KChart::TextAttributes attributes = d->kdLegend->titleTextAttributes(); 0250 attributes.setFontSize(KChart::Measure(size, KChartEnums::MeasureCalculationModeAbsolute)); 0251 d->kdLegend->setTitleTextAttributes(attributes); 0252 0253 d->pixmapRepaintRequested = true; 0254 } 0255 0256 LegendExpansion Legend::expansion() const 0257 { 0258 return d->expansion; 0259 } 0260 0261 void Legend::setExpansion(LegendExpansion expansion) 0262 { 0263 d->expansion = expansion; 0264 d->kdLegend->setOrientation(LegendExpansionToQtOrientation(expansion)); 0265 d->pixmapRepaintRequested = true; 0266 0267 emit updateConfigWidget(); 0268 } 0269 0270 Qt::Alignment Legend::alignment() const 0271 { 0272 return d->alignment; 0273 } 0274 0275 void Legend::setAlignment(Qt::Alignment alignment) 0276 { 0277 d->alignment = alignment; 0278 } 0279 0280 Position Legend::legendPosition() const 0281 { 0282 return d->position; 0283 } 0284 0285 void Legend::setLegendPosition(Position position) 0286 { 0287 d->position = position; 0288 d->pixmapRepaintRequested = true; 0289 } 0290 // Note that size is controlled by the KChart::Legend 0291 // via the propertyChanged() signal 0292 // so size will change dependent on amount of data 0293 void Legend::setSize(const QSizeF &newSize) 0294 { 0295 KoShape::setSize(newSize); 0296 } 0297 0298 0299 void Legend::paintPixmap(QPainter &painter, const KoViewConverter &converter) 0300 { 0301 // Adjust the size of the painting area to the current zoom level 0302 const QSize paintRectSize = converter.documentToView(d->lastSize).toSize(); 0303 d->image = QImage(paintRectSize, QImage::Format_ARGB32); 0304 0305 QPainter pixmapPainter(&d->image); 0306 pixmapPainter.setRenderHints(painter.renderHints()); 0307 pixmapPainter.setRenderHint(QPainter::Antialiasing, false); 0308 0309 // Scale the painter's coordinate system to fit the current zoom level. 0310 applyConversion(pixmapPainter, converter); 0311 d->kdLegend->paint(&pixmapPainter); 0312 } 0313 0314 void Legend::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) 0315 { 0316 //painter.save(); 0317 0318 // First of all, scale the painter's coordinate system to fit the current zoom level 0319 applyConversion(painter, converter); 0320 0321 // Calculate the clipping rect 0322 QRectF paintRect = QRectF(QPointF(0, 0), size()); 0323 //clipRect.intersected(paintRect); 0324 painter.setClipRect(paintRect, Qt::IntersectClip); 0325 0326 // Get the current zoom level 0327 QPointF zoomLevel; 0328 converter.zoom(&zoomLevel.rx(), &zoomLevel.ry()); 0329 0330 // Only repaint the pixmap if it is scheduled, the zoom level changed or the shape was resized 0331 /*if ( d->pixmapRepaintRequested 0332 || d->lastZoomLevel != zoomLevel 0333 || d->lastSize != size()) { 0334 // TODO: What if two zoom levels are constantly being requested? 0335 // At the moment, this *is* the case, due to the fact 0336 // that the shape is also rendered in the page overview 0337 // in Stage 0338 // Every time the window is hidden and shown again, a repaint is 0339 // requested --> laggy performance, especially when quickly 0340 // switching through windows 0341 d->pixmapRepaintRequested = false; 0342 d->lastZoomLevel = zoomLevel; 0343 d->lastSize = size(); 0344 0345 paintPixmap(painter, converter); 0346 }*/ 0347 0348 // Paint the background 0349 if (background()) { 0350 QPainterPath p; 0351 p.addRect(paintRect); 0352 background()->paint(painter, converter, paintContext, p); 0353 } 0354 0355 disconnect (d->kdLegend, SIGNAL(propertiesChanged()), this, SLOT(slotKdLegendChanged())); 0356 0357 // KChart thinks in pixels, Calligra in pt 0358 ScreenConversions::scaleFromPtToPx(painter); 0359 const QRect rect = ScreenConversions::scaleFromPtToPx(paintRect, painter); 0360 // KChart works with its logicalDpi which may differ from ours if set with --dpi <x,y> 0361 ScreenConversions::scaleToWidgetDpi(d->kdLegend, painter); 0362 d->kdLegend->paint(&painter, rect); 0363 0364 connect (d->kdLegend, SIGNAL(propertiesChanged()), this, SLOT(slotKdLegendChanged())); 0365 } 0366 0367 0368 // ---------------------------------------------------------------- 0369 // loading and saving 0370 0371 0372 bool Legend::loadOdf(const KoXmlElement &legendElement, 0373 KoShapeLoadingContext &context) 0374 { 0375 KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); 0376 styleStack.clear(); 0377 0378 // FIXME: If the style isn't present we shouldn't care about it at all 0379 // and move everything related to the legend style in this if clause 0380 if (legendElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { 0381 context.odfLoadingContext().fillStyleStack(legendElement, KoXmlNS::chart, "style-name", "chart"); 0382 styleStack.setTypeProperties("graphic"); 0383 } 0384 0385 if (!legendElement.isNull()) { 0386 int attributesToLoad = OdfAllAttributes; 0387 QString lp = legendElement.attributeNS(KoXmlNS::chart, "legend-position", QString()); 0388 0389 // Note: load position even if it might not be used 0390 loadOdfAttributes(legendElement, context, attributesToLoad); 0391 0392 QString lalign = legendElement.attributeNS(KoXmlNS::chart, "legend-align", QString()); 0393 0394 if (legendElement.hasAttributeNS(KoXmlNS::style, "legend-expansion")) { 0395 QString lexpansion = legendElement.attributeNS(KoXmlNS::style, "legend-expansion", QString()); 0396 if (lexpansion == "wide") 0397 setExpansion(WideLegendExpansion); 0398 else if (lexpansion == "high") 0399 setExpansion(HighLegendExpansion); 0400 else 0401 setExpansion(BalancedLegendExpansion); 0402 } 0403 0404 if (lalign == "start") { 0405 setAlignment(Qt::AlignLeft); 0406 } 0407 else if (lalign == "end") { 0408 setAlignment(Qt::AlignRight); 0409 } 0410 else { 0411 setAlignment(Qt::AlignCenter); // default 0412 } 0413 0414 if (lp == "start") { 0415 setLegendPosition(StartPosition); 0416 } 0417 else if (lp == "top") { 0418 setLegendPosition(TopPosition); 0419 } 0420 else if (lp == "bottom") { 0421 setLegendPosition(BottomPosition); 0422 } 0423 else if (lp == "end") { 0424 setLegendPosition(EndPosition); 0425 } 0426 else if (lp == "top-start") { 0427 setLegendPosition(TopStartPosition); 0428 } 0429 else if (lp == "bottom-start") { 0430 setLegendPosition(BottomStartPosition); 0431 } 0432 else if (lp == "top-end") { 0433 setLegendPosition(TopEndPosition); 0434 } 0435 else if (lp == "bottom-end") { 0436 setLegendPosition(BottomEndPosition); 0437 } else { 0438 setLegendPosition(FloatingPosition); 0439 } 0440 0441 if (legendElement.hasAttributeNS(KoXmlNS::office, "title")) { 0442 setTitle(legendElement.attributeNS(KoXmlNS::office, "title", QString())); 0443 } 0444 0445 styleStack.setTypeProperties("text"); 0446 0447 if (styleStack.hasProperty(KoXmlNS::fo, "font-family")) { 0448 QString fontFamily = styleStack.property(KoXmlNS::fo, "font-family"); 0449 QFont font = d->font; 0450 font.setFamily(fontFamily); 0451 setFont(font); 0452 } 0453 if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) { 0454 qreal fontSize = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "font-size")); 0455 setFontSize(fontSize); 0456 } 0457 if (styleStack.hasProperty(KoXmlNS::fo, "font-color")) { 0458 QColor color = styleStack.property(KoXmlNS::fo, "font-color"); 0459 if (color.isValid()) { 0460 setFontColor(color); 0461 } 0462 } 0463 } 0464 else { 0465 // No legend element, use default legend. 0466 setLegendPosition(EndPosition); 0467 setAlignment(Qt::AlignCenter); 0468 } 0469 0470 d->pixmapRepaintRequested = true; 0471 0472 return true; 0473 } 0474 0475 void Legend::saveOdf(KoShapeSavingContext &context) const 0476 { 0477 KoXmlWriter &bodyWriter = context.xmlWriter(); 0478 0479 bodyWriter.startElement("chart:legend"); 0480 saveOdfAttributes(context, OdfPosition); 0481 0482 // Legend specific attributes 0483 QString lp = PositionToString(d->position); 0484 if (!lp.isEmpty()) { 0485 bodyWriter.addAttribute("chart:legend-position", lp); 0486 } 0487 QString lalign; 0488 switch (d->alignment) { 0489 case Qt::AlignLeft: lalign = "start"; break; 0490 case Qt::AlignRight: lalign = "end"; break; 0491 case Qt::AlignCenter: lalign = "center"; break; 0492 default: break; 0493 } 0494 if (!lalign.isEmpty()) { 0495 bodyWriter.addAttribute("chart:legend-align", lalign); 0496 } 0497 0498 // Legend style FIXME: Check if more styling then just the font goes here. 0499 KoGenStyle style(KoGenStyle::ChartAutoStyle, "chart", 0); 0500 OdfHelper::saveOdfFont(style, d->font, d->fontColor); 0501 bodyWriter.addAttribute("chart:style-name", saveStyle(style, context)); 0502 0503 QString lexpansion; 0504 switch (expansion()) { 0505 case WideLegendExpansion: lexpansion = "wide"; break; 0506 case HighLegendExpansion: lexpansion = "high"; break; 0507 case BalancedLegendExpansion: lexpansion = "balanced"; break; 0508 }; 0509 bodyWriter.addAttribute("style:legend-expansion", lexpansion); 0510 0511 if (!title().isEmpty()) 0512 bodyWriter.addAttribute("office:title", title()); 0513 0514 bodyWriter.endElement(); // chart:legend 0515 } 0516 0517 KChart::Legend *Legend::kdLegend() const 0518 { 0519 // There has to be a valid KChart instance of this legend 0520 Q_ASSERT(d->kdLegend); 0521 return d->kdLegend; 0522 } 0523 0524 void Legend::rebuild() 0525 { 0526 d->kdLegend->forceRebuild(); 0527 update(); 0528 } 0529 0530 void Legend::update() const 0531 { 0532 d->pixmapRepaintRequested = true; 0533 KoShape::update(); 0534 } 0535 0536 void Legend::slotKdLegendChanged() 0537 { 0538 // FIXME: Update legend properly by implementing all *DataChanged() slots 0539 // in KChartModel. Right now, only yDataChanged() is implemented. 0540 //d->kdLegend->forceRebuild(); 0541 QSizeF size = ScreenConversions::scaleFromPxToPt(d->kdLegend->sizeHint()); 0542 setSize(ScreenConversions::fromWidgetDpi(d->kdLegend, size)); 0543 update(); 0544 } 0545 0546 void Legend::slotChartTypeChanged(ChartType chartType) 0547 { 0548 // TODO: Once we support markers, this switch will have to be 0549 // more clever. 0550 switch (chartType) { 0551 case LineChartType: 0552 case ScatterChartType: 0553 d->kdLegend->setLegendStyle(KChart::Legend::MarkersAndLines); 0554 break; 0555 default: 0556 d->kdLegend->setLegendStyle(KChart::Legend::MarkersOnly); 0557 break; 0558 } 0559 }