This section describes how to create your own item types for KReport by implementing a sample plugin that adds a filled rectangle to the report.
Base Item
The duties of the base item is to extract values of its properties, such as position or size, from the report’s XML, and to create the rendering primitives to draw from the given data.
KReport will give us the XML data in the form of a QDomNode object that only contains the fragment relevant to our item. In this case, the XML structure will be something like the following:
<report:rect report:name="rect1"
report:z-index="0"
svg:x="113.386023318759058pt"
svg:y="21.259879372267321pt"
svg:height="40.500000848363968pt"
svg:width="210.000004398924290pt"
fo:background-color="#ffffff"
fo:background-opacity="0%"
>
<report:line-style report:line-style="solid"
report:line-weight="1"
report:line-color="#000000"
/>
</report:rect>
As for the rendering primitives, there are two type of items: these that create them based on a simple value from the data source, for instance a label, and these that use a subquery to extract the data, as is the case for charts; the former need to implement KReportItemBase::renderSimpleData while the latter need KReportItemBase::renderReportData. Rendering a rectangle does not need data from a source and will be a simple item that ignores its input.
#include <KReportItemBase>
{
Q_OBJECT
public:
KReportRectItem();
explicit KReportRectItem(
const QDomNode &node);
~KReportRectItem() override;
KReportScriptHandler *script) override;
protected:
void createProperties() override;
}
Base class for items that are drawn syncronously.
virtual int renderSimpleData(OROPage *page, OROSection *section, const QPointF &offset, const QVariant &data, KReportScriptHandler *script)
Render the item into a primitive which is used by the second stage renderer.
virtual QString typeName() const =0
Return the item type as a string.
The KReportLineStyle class represents line style.
Represents a single page in a document and may contain zero or more OROPrimitive objects all of which...
Represents a single a single row in a document and may contain zero or more OROPrimitives.
As we will see later, the default constructor will be used when creating an empty item from the designer and it must create all properties except for the name, size, position, and data source, because these are created by KReportItemBase:
#include "kreportrectitem.h"
#include <KProperty>
#include <KPropertySet>
KReportRectItem::KReportRectItem()
{
createProperties();
}
void KReportRectItem::createProperties()
{
m_backgroundOpacity =
new KProperty(
"background-opacity",
QVariant(0), tr(
"Background Opacity"));
m_backgroundOpacity->setOption("max", 100);
m_backgroundOpacity->setOption("min", 0);
m_backgroundOpacity->setOption("suffix", "%");
m_lineWeight =
new KProperty(
"line-weight", 1.0, tr(
"Line Weight"));
m_lineWeight->setOption("step", 1.0);
propertySet()->addProperty(m_backgroundColor);
propertySet()->addProperty(m_backgroundOpacity);
propertySet()->addProperty(m_lineWeight);
propertySet()->addProperty(m_lineColor);
propertySet()->addProperty(m_lineStyle);
}
As you can see, we create a KProperty object for each property that can be set and then we add them all to the item’s property set, that will manage their life cycle and signals.
The constructor accepting a QDomNode must extract the properties’ value from it. Fortunately, KReportItemBase and KReportUtils already contain functions to help us in reading common properties.
#include <KReportUtils>
#include <QDebug>
#include <QDomNode>
KReportRectItem::KReportRectItem(
const QDomNode &node)
: KReportRectItem()
{
nameProperty()->setValue(KReportUtils::readNameAttribute(element));
setZ(KReportUtils::readZAttribute(element));
parseReportRect(element);
QColor backgroundColor(element.
attribute(
"fo:background-color",
"#ffffff"));
m_backgroundColor->setValue(backgroundColor);
bool ok;
int backgroundOpacity = KReportUtils::readPercent(element, "fo:background-opacity", 100, &ok);
if (ok) {
m_backgroundOpacity->setValue(backgroundOpacity);
}
for (
int i = 0; i < children.
count(); i++) {
if (name == "report:line-style") {
if (parseReportLineStyleData(node.
toElement(), &style)) {
m_lineWeight->setValue(style.
weight());
m_lineColor->setValue(style.
color());
m_lineStyle->setValue(
static_cast<int>(style.
penStyle()));
}
} else {
qWarning() <<
"found unknown element while parsing rect element:" <<
name;
}
}
}
QString name(StandardAction id)
QString attribute(const QString &name, const QString &defValue) const const
QDomNodeList childNodes() const const
QString nodeName() const const
QDomElement toElement() const const
QDomNode at(int index) const const
Notice how we can call nameProperty() to set this item’s name even though we did not create that property. Also, parseReportRect will read the svg:x
, svg:y
, svg:width
and svg:height
attributes from the node’s element and initialize the corresponding position and size properties that we have inherited from KReportItemBase.
Now we are ready to render rectangles to the report’s output. renderSimpleData receives the page and the section that the item needs to render to add the rendering objects to, and must return how much it stretches the section’s height, for instance as a result of wrapping text. Both are nullptr
when KReport is computing the actual section’s height.
data
contains the value that the item must render and is given by the data source. For a rectangle we already got all the information required from the XML and can safely ignore this parameter.
In this case we only need to create an ORORect object and set all its attributes from the item’s properties. KReportItemBase::scenePosition and KReportItemBase::sceneSize convert, respectively, the position and size from point units to pixels according to the output’s DPI.
#include <KReportUnit>
#include <QPointF>
#include <QRectF>
int KReportRectItem::renderSimpleData(
OROPage *page,
KReportScriptHandler *script)
{
Q_UNUSED(data)
Q_UNUSED(script)
rect->setRect(
QRectF(scenePosition(position()) + offset, sceneSize(size())));
rect->setPen(pen());
rect->setBrush(brush());
if (page) {
page->insertPrimitive(rect);
}
if (section) {
clone->setPosition(scenePosition(position()));
section->addPrimitive(clone);
}
if (!page) {
delete rect;
}
return 0;
}
QBrush KReportRectItem::brush()
const
{
color.
setAlphaF(m_backgroundOpacity->value().toReal() * 0.01);
}
{
style.setWeight(m_lineWeight->value().toReal());
style.setColor(m_lineColor->value().value<
QColor>());
style.setPenStyle(
static_cast<Qt::PenStyle>(m_lineStyle->value().toInt()));
return style;
}
QPen KReportRectItem::pen()
const
{
}
Represents the basic primitive with a position and type. Other primitives are subclasses with a defin...
void setAlphaF(float alpha)
The only remaining bit is to tell KReport the item‘s name, that will be the basis for its XML tag. That is, "<report:" + typeName() + ">"
.
QString KReportRectItem::typeName()
const
{
return "rect";
}
Designer Item
The designer item extends from the base item and its duties are to build the XML node that will be placed in the report’s structure, and to draw a preview in the designer’s QGraphicsScene. This object can start its life with the properties at their default values or by initializing their values from a QDomNode, like the base item does.
KReportDesignerItemRectBase is a class that extends KReportDesignerItemBase and is used as the base class for items that are “rectangular”. This, of course, includes our rectangle, but also items such as fields and labels because they define a rectangular area where they will render its data in. Extending from this class means that we do not need to take care of handling mouse events and can easily draw the item’s resize handles when it is selected.
#include "kreportrectitem.h"
#include <KReportDesignerItemRectBase>
{
Q_OBJECT
public:
~KReportRectDesignerItem() override;
KReportRectDesignerItem *clone() override;
private Q_SLOTS:
private:
};
Base class for rectangular report items used within the designer GUI.
The ReportDesigner is the main widget for designing a report.
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
The constructor accepting a QDomNode is called when the designer loads an existing document and we simply call the base item’s constructor to initialize the properties from the XML node. The other constructor is called when the user creates a new item on the designer widget and we need to create a default-valuated instance; notice how we ask KReportDesigner to suggest us a name based on the item’s type name. In both cases we have to initialize this item in the scene graph and setup a slot that will inform the designer when a property has changed.
#include "kreportrectdesigneritem.h"
#include <KProperty>
#include <KReportDesigner>
#include <QGraphicsScene>
KReportRectDesignerItem::KReportRectDesignerItem(
KReportDesigner *designer,
: KReportRectItem()
{
Q_UNUSED(pos)
init(scene);
setSceneRect(properRect(*designer, size, size));
}
KReportRectDesignerItem::KReportRectDesignerItem(
const QDomNode &node,
: KReportRectItem(node)
{
init(scene);
}
{
if (scene) {
}
this,
&KReportRectDesignerItem::slotPropertyChanged);
setFlags(ItemIsSelectable | ItemIsMovable | ItemSendsGeometryChanges);
setZValue(z());
}
if (property.
name() ==
"name") {
if (!designer()->isEntityNameUnique(property.
value().
toString(),
this)) {
property.setValue(oldName());
} else {
}
}
KReportDesignerItemRectBase::propertyChanged(set, property);
if (designer()) {
}
}
void propertyChanged(KPropertySet &set, KProperty &property)
QString suggestEntityName(const QString &name) const
Return a unique name that can be used by the entity.
void setModified(bool modified)
Sets the modified status, defaulting to true for modified.
static QPointF scenePosition(const QPointF &ptPos)
Helper function mapping to screen units (pixels), ptPos is in points.
static QSizeF sceneSize(const QSizeF &ptSize)
Helper function mapping to screen units (pixels), ptSize is in points.
static qreal parseValue(const QString &value, qreal defaultVal=0.0)
Parses common KReport and ODF values, like "10cm", "5mm" to pt.
KCRASH_EXPORT void setFlags(KCrash::CrashFlags flags)
void addItem(QGraphicsItem *item)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QString toString() const const
When the designer wants to write the report’s template in XML, it will call buildXML()
with the XML document that is creating and the node that needs to be this item’s parent. Our job is to create the same XML structure that the base item’s constructor accepting a QDomNode can read. Again, KReportDesignerItemBase has utility functions to help us build the XML of common elements and attributes.
#include <QDomDocument>
#include <QDomElement>
{
addPropertyAsAttribute(&entity, nameProperty());
buildXMLRect(doc, &entity, this);
buildXMLLineStyle(doc, &entity, lineStyle());
}
QString name(NameFormat format) const const
QDomElement createElement(const QString &tagName)
void setAttribute(const QString &name, const QString &value)
QDomNode appendChild(const QDomNode &newChild)
QString number(double n, char format, int precision)
In fact, we can use this dynamic of buildXML()
generating the XML node that the constructor can read to implement the required clone
method. This method is called by KReportDesigner to copy and paste items, among others.
KReportRectDesignerItem *KReportRectDesignerItem::clone()
{
buildXML(&doc, &parent);
return new KReportRectDesignerItem(node, designer(), nullptr);
}
QDomNode firstChild() const const
The last thing to do is draw the actual item on the designer. In contrast to KReportItemBase, KReportDesignerItemRectBase is a QGraphicsItem-derived class and must know how to paint itself using a QPainter. For a rectangle we only have to take the same brush and pen that we use when building a ORORect and paint a rectangle at the current position and size. KReportDesignerItemRectBase::drawHandles will render the eight small boxes all around the rectangle that allow users to resize our item with the mouse.
void KReportRectDesignerItem::paint(
QPainter *painter,
{
Q_UNUSED(option)
Q_UNUSED(widget)
painter->
setPen(KReportRectItem::pen());
painter->
setBrush(KReportRectItem::brush());
drawHandles(painter);
}
QRectF rect() const const
void drawRect(const QRect &rectangle)
void setBrush(Qt::BrushStyle style)
void setPen(Qt::PenStyle style)
Scripting
KReport allows users to write scripts in JavaScript to change items’ properties. For example, the following script for a report named example_report
would change the background color of an item named rect1 to the color #abc, and toggle the section’s between #ffffff and #dddddd prior to rendering it:
function detail() {
var count = 0;
this.OnRender = function() {
count++;
if (count % 2 == 0) {
example_report.section_detail.backgroundColor = "#ffffff";
} else {
example_report.section_detail.backgroundColor = "#dddddd";
}
example_report.section_detail.objectByName("rect1").backgroundColor = "#abc";
}
}
example_report.section_detail.initialize(new detail());
KReport uses QML’s QJSEngine to run JavaScript and calling objectByName
from the script will try to create a QObject-derived object that wraps the item whose name is passed as parameter. This QObject needs to define Qt properties in order to provide a way to write and read item's property values. By convention, the scripting class is from the Scripting
namespace.
#include <QObject>
class KReportRectItem;
{
Q_OBJECT
Q_PROPERTY(
QColor backgroundColor READ backgroundColor WRITE setBackgroundColor)
Q_PROPERTY(int backgroundOpacity READ backgroundOpacity WRITE setBackgroundOpacity)
Q_PROPERTY(
QColor lineColor READ lineColor WRITE setLineColor)
Q_PROPERTY(int lineStyle READ lineStyle WRITE setLineStyle)
Q_PROPERTY(int lineWeight READ lineWeight WRITE setLineWeight)
Q_PROPERTY(
QPointF position READ position WRITE setPosition)
Q_PROPERTY(
QSizeF size READ size WRITE setSize)
public:
explicit Rect(KReportRectItem *item,
QObject *parent =
nullptr);
QColor backgroundColor()
const;
int backgroundOpacity() const;
int lineStyle() const;
int lineWeight() const;
public Q_SLOTS:
void setBackgroundColor(
const QColor &color);
void setBackgroundOpacity(int opacity);
void setLineColor(
const QColor &color);
void setLineStyle(int style);
void setLineWeight(int weight);
void setPosition(
const QPointF &position);
void setSize(
const QSizeF &size);
private:
KReportRectItem *m_rect;
};
}
Field item script interface.
The implementation is very straightforward and just needs to set or get the item’s property values.
#include "kreportrectscript.h"
#include "kreportrectitem.h"
#include <KProperty>
#include <QPointF>
#include <QSizeF>
Scripting::Rect::Rect(KReportRectItem *item,
QObject *parent)
, m_rect(item)
{}
QColor Scripting::Rect::backgroundColor()
const
{
return m_rect->m_backgroundColor->
value().value<
QColor>();
}
int Scripting::Rect::backgroundOpacity() const
{
return m_rect->m_backgroundOpacity->
value().toInt();
}
QColor Scripting::Rect::lineColor()
const
{
}
int Scripting::Rect::lineStyle() const
{
return m_rect->m_lineStyle->
value().toInt();
}
int Scripting::Rect::lineWeight() const
{
return m_rect->m_lineWeight->value().toInt();
}
QPointF Scripting::Rect::position()
const
{
return m_rect->position();
}
QSizeF Scripting::Rect::size()
const
{
return m_rect->size();
}
void Scripting::Rect::setBackgroundColor(
const QColor &color)
{
m_rect->m_backgroundColor->setValue(color);
}
void Scripting::Rect::setBackgroundOpacity(int opacity)
{
m_rect->m_backgroundOpacity->setValue(opacity);
}
void Scripting::Rect::setLineColor(
const QColor &color)
{
m_rect->m_lineColor->setValue(color);
}
void Scripting::Rect::setLineStyle(int style)
{
m_rect->m_lineStyle->setValue(qMax(1, qMin(style, 5)));
}
void Scripting::Rect::setLineWeight(int weight)
{
m_rect->m_lineWeight->setValue(weight);
}
void Scripting::Rect::setPosition(
const QPointF &position)
{
m_rect->setPosition(position);
}
void Scripting::Rect::setSize(
const QSizeF &size)
{
m_rect->setSize(size);
}
Because most of KReportRectItem properties are private, we need to make Scripting::Rect
a friend class by appending the following at the end of KReportRectItem’s declaration:
private:
friend class Scripting::Rect;
Implementing the Plugin Interface
We already have all the actors ready — the base item class, the designer’s, and the class that allows changing its properties from scripts —, but we need a way to somehow tell KReport that they exist. This is done by implementing the KReportPluginInterface interface.
#include <KReportPluginInterface>
{
Q_OBJECT
public:
explicit KReportRectPlugin(
QObject *parent,
const QVariantList &args = QVariantList());
~KReportRectPlugin() override;
#ifdef KREPORT_SCRIPTING
#endif
};
An interface for plugins delivering KReport elements.
virtual QObject * createRendererInstance(const QDomNode &element)=0
KReportPluginInterface::createRendererInstance is called when there is the need to instantiate a base item for rendering the report. It is given the QDomNode from the report’s XML template of this item.
#include "kreportrectitem.h"
QObject *KReportRectPlugin::createRendererInstance(
const QDomNode &element)
{
return new KReportRectItem(element);
}
.
Likewise, KReportPluginInterface::createDesignerInstance is called to instantiate a designer item. As we saw above, the designer item can be needed when loading an XML document or when the user creates a new item in the designer widget, that is why there are two different signatures for this method.
#include "kreportrectdesigneritem.h"
QObject *KReportRectPlugin::createDesignerInstance(
const QDomNode &element,
{
return new KReportRectDesignerItem(element, designer, scene);
}
{
return new KReportRectDesignerItem(designer, scene, pos);
}
The last class we need to instantiate is the QObject-derived object used in scripts. The method receives the item to wrap as a parameter. Due to the dynamic nature of JavaScript, we need to cast it to the type of our item and make sure it is correct.
#ifdef KREPORT_SCRIPTING
#include "kreportrectscript.h"
{
auto rect = qobject_cast<KReportRectItem *>(item);
if (!rect) {
return nullptr;
}
return new Scripting::Rect(rect);
}
#endif
The last thing required to implement this interface is to create a plugin factory. It can be done with the K_PLUGIN_CLASS_WITH_JSON macro, that expects the name of the class extending KReportPluginInterface and the name of a JSON file with valid KPlugin metadata. It is also necessary to include the source code generated by the Qt MOC in the plugin’s implementation file in order to compile the generated factory code.
#include "kreportrectplugin.h"
KReportRectPlugin::KReportRectPlugin(
QObject *parent, const QVariantList &args)
{}
KReportRectPlugin::~KReportRectPlugin() {}
#include "kreportrectplugin.moc"
#define K_PLUGIN_CLASS_WITH_JSON(classname, jsonFile)
The JSON file must have all necessary KPlugin metadata. Also, "Version" must be set to the major and minor component of KReport’s stable version. For example, in KReport 3.2.1 "Version" must be set to "3.2".
Lastly, the JSON file must contain a "X-KDE-PluginInfo-LegacyName" property with the exact same value as the return value of KReportItemBase::typeName or KReport would fail to find the plugin when reading XML templates or creating new items in the designer.
{
"KPlugin": {
"Authors": [
{
"Email": "author@hosting.suffix",
"Name": "Proud Author"
}
],
"Category": "",
"Description": "Rectangle element for Reports",
"Icon": "draw-rectangle",
"Id": "org.kde.kreport.rect",
"License": "LGPL",
"Name": "Rect",
"Version": "3.3"
},
"X-KDE-PluginInfo-LegacyName": "rect",
"X-KReport-PluginInfo-Priority": "100"
}
The last piece you need for a complete item plugin is a CMakeLists.txt
that compiles and installs the plugin. The CMakeLists.txt
looks like the following:
find_package(ECM 1.8.0 NO_MODULE REQUIRED)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
include(KDEInstallDirs)
include(KDECMakeSettings NO_POLICY_SCOPE)
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt5 5.4.0 COMPONENTS Core REQUIRED)
find_package(KReport 3.2.90 NO_MODULE REQUIRED)
set(kreportrectplugin_SRCS
kreportrectdebug.cpp
kreportrectdesigneritem.cpp
kreportrectitem.cpp
kreportrectplugin.cpp
)
if (KREPORT_SCRIPTING)
list(APPEND kreportrectplugin_SRCS
kreportrectscript.cpp
)
endif(KREPORT_SCRIPTING)
add_library(KReportRectPlugin MODULE ${kreportrectplugin_SRCS})
target_link_libraries(KReportRectPlugin PRIVATE Qt5::Core KReport)
install(TARGETS KReportRectPlugin DESTINATION ${KDE_INSTALL_PLUGINDIR}/kreport3)
Now you can compile the type plugin and install it. Once done, the new plugin should be available for every application using KReport. In applications that utilize the Report Designer, Items toolbar should also offer the new item type.