Hi, I am working in Train Traffic Controller software project. My responsibility in this project is to develop the visual railroad GUI.
We are implementing the project with Qt.
By now I am using QGraphicsLinearLayout to hold my items.
I am using the layout because I do not want to calculate coordinates of each item.
So far I wrote item classes to add the layout. For instance SwitchItem class symbolizes
railroad switch in real world. Each item class is responsible for its own painting and events.
So far so good.
Now I need a composite item that can contain two or more item. This class is going to be responsible for painting the items contained in it. I need this class because I have to put two or more items inside same layout cell. If I don' t put them in same cell I can' t use layout. See the image below.
BlockSegmentItem and SignalItem inside same cell.
CompositeItem header file
#include <QtCore/QList>
#include <QtGui/QGraphicsLayoutItem>
#include <QtGui/QGraphicsItemGroup>
#include <QtGui/QGraphicsSceneMouseEvent>
#include <QtGui/QGraphicsSceneContextMenuEvent>
#include "fielditem.h"
class CompositeItem : public FieldItem
{
Q_OBJECT
public:
CompositeItem(QString id,QList<FieldItem *> _children);
~CompositeItem();
QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF(-1,-1)) const;
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget /* = 0 */);
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event);
private:
QList<FieldItem *> children;
};
//CompositeItem implementation
#include "compositeitem.h"
CompositeItem::CompositeItem(QString id,QList<FieldItem *> _children)
{
children = _children;
}
CompositeItem::~CompositeItem()
{
}
QRectF CompositeItem::boundingRect() const
{
FieldItem *child;
QRectF rect(0,0,0,0);
foreach(child,children)
{
rect = rect.united(child->boundingRect());
}
return rect;
}
void CompositeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget )
{
FieldItem *child;
foreach(child,children)
{
child->paint(painter,option,widget);
}
}
QSizeF CompositeItem::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
{
QSizeF itsSize(0,0);
FieldItem *child;
foreach(child,children)
{
// if its size empty set first child size to itsSize
if(itsSize.isEmpty())
itsSize = child->sizeHint(Qt::PreferredSize);
else
{
QSizeF childSize = child->sizeHint(Qt::PreferredSize);
if(itsSize.width() < childSize.width())
itsSize.setWidth(childSize.width());
itsSize.setHeight(itsSize.height() + childSize.height());
}
}
return itsSize;
}
void CompositeItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
qDebug()<<"Test";
}
//Code that add items to scene
//Subclass of QGraphicsWidget for future extension
FieldItemContainer *widget;
while(!itemGroupNode.isNull())
{
//Subclass of QGraphicsLinearLayout for future extension
FieldItemGroupLayout *layout = new FieldItemGroupLayout;
int layoutX = itemGroupNode.toElement().attribute("X").toInt();
int layoutY = itemGroupNode.toElement().attribute("Y").toInt();
layout->setSpacing(1);
QDomNode itemNode = itemGroupNode.toElement().namedItem("Item");
while (!itemNode.isNull() && itemNode.nodeType() == QDomNode::ElementNode)
{
FieldItem * item;
//Create proper item
item = FieldItemFactory::createFieldItem(itemNode);
//Add item to layout
layout->addItem(item);
itemNode = itemNode.nextSibling();
}
widget = new FieldItemContainer;
//Set layout to widget
widget->setLayout(layout);
widget->setPos(layoutX,layoutY);
//Add widget to scene
itsScene->addItem(widget);
itemGroupNode = itemGroupNode.nextSibling();
}
//FieldItemFactory implementation
FieldItem* FieldItemFactory::createFieldItem(QDomNode itemNode)
{
FieldItem *item;
//Common for all items
QString itemId = itemNode.toElement().attribute("id");
QString itemType = itemNode.toElement().attribute("type");
int x1 = itemNode.namedItem("X1").toElement().text().toInt();
int y1 = itemNode.namedItem("Y1").toElement().text().toInt();
//Only for Block part items
int x2 = 0;
int y2 = 0;
QString signalization = "";
////***********************
//Only for signal items
QString source = "";
int width = 0;
int height = 0;
///********************
//Only for switch items
QString normalPositionSource = "";
QString reversePositionSource = "";
///********************
///Labeling
QDomNode itemLabelNode = itemNode.namedItem("Label");
FieldItemLabel label;
label.x1 = itemLabelNode.namedItem("X1").toElement().text().toInt();
label.y1 = itemLabelNode.namedItem("Y1").toElement().text().toInt();
label.text = itemLabelNode.namedItem("Text").toElement().text().trimmed();
label.rotateAngle = itemLabelNode.namedItem("RotateAngle").toElement().text().toInt();
label.hideScale = itemLabelNode.namedItem("HideScale").toElement().text().toInt();
///*****************
if(itemType == FieldProperty::BLOCKSEGMENTITEM)
{
x2 = itemNode.namedItem("X2").toElement().text().toInt();
y2 = itemNode.namedItem("Y2").toElement().text().toInt();
signalization = itemNode.namedItem("Signalization").toElement().text().trimmed();
item = new BlockSegmentItem(itemId,x1,y1,x2,y2,
label,signalization);
}
else if(itemType == FieldProperty::SWITCHITEM)
{
normalPositionSource = itemNode.namedItem("Normal").toElement().text().trimmed();
reversePositionSource = itemNode.namedItem("Reverse").toElement().text().trimmed();
item = new SwitchItem(itemId,x1,y1,FieldProperty::SWITCH_WIDTH,
FieldProperty::SWITCH_HEIGHT,label,
normalPositionSource,reversePositionSource);
}
else if(itemType == FieldProperty::SIGNALITEM)
{
source = itemNode.namedItem("Source").toElement().text().trimmed();
width = itemNode.namedItem("Width").toElement().text().toInt();
height = itemNode.namedItem("Height").toElement().text().toInt();
item = new SignalItem(itemId,x1,y1,width,height,label,source);
}
else if(itemType == FieldProperty::TRAINIDBOXITEM)
{
item = new TrainIdBox(itemId,x1,y1);
}
else if(itemType == FieldProperty::COMPOSITEITEM)
{
QList<FieldItem *> children;
for (int i = 0; i < itemNode.childNodes().count(); i++)
{
children.push_back(createFieldItem(itemNode.childNodes().at(i)));
}
item = new CompositeItem(itemId,children);
}
return item;
}
//At last part of xml data that I used to create BlockSegmentItem and SignalItem in same cell
<Item id="CI5@MIT" type="COMPOSITE">
<Item id="001BT@MIT" type="BLOCKSEGMENT">
<Label>
<Text>001BT</Text>
<X1>70</X1>
<Y1>10</Y1>
</Label>
<X1>0</X1>
<Y1>15</Y1>
<X2>150</X2>
<Y2>15</Y2>
</Item>
<Item id="B2D@MIT" type="SIGNAL">
<Label>
<Text>B2D</Text>
<X1>15</X1>
<Y1>40</Y1>
</Label>
<X1>0</X1>
<Y1>20</Y1>
<Width>40</Width>
<Height>10</Height>
<Source>Resources/graphics/signals/sgt3lr.svg</Source>
</Item>
</Item>
This code works good with painting but when it comes to item events it is problematic. QGraphicsScene treats the composite item like a single item which is right for layout but not for events. Because each item has its own event implementation.(e.g. SignalItem has its special context menu event.)
I have to handle item events seperately. Also I need a composite item implementation for the layout. How can I overcome this dilemma?