views:

124

answers:

1

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.

Composite Item 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?

A: 

You can use the QGraphicsScene::sendEvent() to pass the event to children. Here is an example that should address your issue.

void CompositeItem::sceneEvent(QEvent * event)
{
  if (scene()) {
    FieldItem *child;
    foreach(child, children) {
      scene()->sendEvent(child, event);
    }
  }
}

It seems to me that composite item can be understood as layered layout with one item drawn on top of the other. Such layout does not exist yet but I wouldn't be surprised if they appeared in the future.

Lohrun
@Lohrun Due to an advice I edited boundingRect code. I guess it is a neat idea to unite childs boundingRects. But this time item can' t catch events like contextmenuevent and mousepressevent. So my prior problem is to catch events. Do you have an idea what may cause this?
onurozcelik
Does your CompositeItem derive from a QGraphicsItem? I am missing the declaration of the CompositeItem class to have a complete view of the problem.
Lohrun
@Lohrun I added the details to original post. Hope that is enough. If you need more explanation just ask ;)
onurozcelik