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 }