Я работаю над программным проектом Train Traffic Controller. В этом проекте я отвечаю за разработку визуального графического интерфейса железной дороги.
Мы реализуем проект с Qt.
Сейчас я использую QGraphicsLinearLayout для хранения моих предметов. Я использую макет, потому что не хочу вычислять координаты каждого элемента.
До сих пор я писал классы элементов, чтобы добавить макет. Например, класс SwitchItem символизирует
железнодорожный переключатель в реальном мире. Каждый класс предметов отвечает за свою картину и события. Все идет нормально.
Теперь мне нужен составной элемент, который может содержать два или более элемента. Этот класс будет отвечать за отрисовку содержащихся в нем элементов. Мне нужен этот класс, потому что мне нужно поместить два или более элемента в одну и ту же ячейку макета. Если я не помещу их в одну ячейку, я не смогу использовать макет. См. изображение ниже.
BlockSegmentItem и SignalItem внутри одной ячейки.
Заголовочный файл CompositeItem
#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>
Этот код хорошо работает с рисованием, но когда дело доходит до событий элементов, возникают проблемы. QGraphicsScene обрабатывает составной элемент, как один элемент, который подходит для макета, но не для событий. Потому что каждый элемент имеет свою собственную реализацию события (например, SignalItem имеет свое специальное событие контекстного меню).
Я должен обрабатывать события элемента отдельно. Также мне нужна реализация составного элемента для макета. Как я могу преодолеть эту дилемму?