PDA

View Full Version : add pixels to qgraphicsitem



mcarter
11th February 2011, 23:16
I have derived a class from QGraphicsRectItem that draw a rect on the screen. Simple enough.

I have enabled selection on this item and the normal dotted frame appears over the displayed rect. Now I want to highlight this rect when selected so I would like to place a selection border around the item's rect.

From what I have read about QGraphicsItems, they should only draw within the boundingRect. I have overridden the boundingRect method and tried to adjust the rect by 1, but as you guess that did not quite work since the QGraphicsRectItem uses the scene's coordinate system. The transform of the item does not seem to help since its type is always TxNone.

The paint event seems to be the only time when I can get a decent transform for the item. Do I need to save that and then apply it in the boundingRect method?

All I want to do is add a few pixels around the QGraphicsRectItem.

wysota
11th February 2011, 23:33
Adjusting boundingRect() by "1" should have worked provided that your scene works in terms of pixels in the 1:1 scale. Please show us your item implementation.

mcarter
12th February 2011, 15:40
My scene is not 1:1 scale, but the transform on the item does not represent that. Is there a way to get the transform of the scene/view?

Right now my implementation is just the basics . . . overriding just the boundingRect and paint methods. The boundingRect is just "return this->rect().adjusted( -1, -1, 1, 1 );" and the paint method is taken straight from QGraphicsRectItem qt code.

wysota
12th February 2011, 17:50
So what exactly does not work?

mcarter
13th February 2011, 03:57
Adding pixels to the QGraphicsRectItem does not work.

So here is some sample code that demonstrates my problem. I have an initial scene and then "zoom" to a particular area. This is where I draw the rectangle. If the rectangle is then selected, a red "hilite" should be drawn around the rectangle . . . and currently its suppose to be penWidth pixels wide. However, it is many pixels wide in the Y direction and nowhere in the X direction.

MyRect.h


#include <QPen>
#include <QDebug>
#include <QPainter>
#include <QGraphicsRectItem>
#include <QStyleOptionGraphicsItem>

class MyRect : public QObject, public QGraphicsRectItem
{
Q_OBJECT

public:
MyRect( QGraphicsItem *pParent=0 )
: QGraphicsRectItem( pParent )
{
setItUp();
}
MyRect( const QRectF & rect, QGraphicsItem* pParent=0 )
: QGraphicsRectItem( rect, pParent )
{
setItUp();
}
~MyRect() {}

void setItUp()
{
m_penWidth = 1.0;
setPen( QPen(Qt::green) );
setAcceptHoverEvents( true );
setFlag( QGraphicsItem::ItemIsSelectable, true );
}

QRectF boundingRect() const
{
return this->rect().adjusted( -m_penWidth, -m_penWidth,
m_penWidth, m_penWidth );
}


protected:
void paint( QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget )
{
Q_UNUSED(widget);
painter->setPen(pen());
painter->setBrush(brush());
painter->drawRect(rect());

if (option->state & QStyle::State_Selected)
{
const QRectF murect = painter->transform().mapRect(QRectF(0, 0, 1, 1));
if (qFuzzyCompare(qMax(murect.width(), murect.height()) + 1, 1))
return;

const QRectF mbrect = painter->transform().mapRect(this->boundingRect());
if (qMin(mbrect.width(), mbrect.height()) < qreal(1.0))
return;

QPen hilitePen(QBrush(Qt::red),m_penWidth);
const qreal pad = hilitePen.widthF() / 2;
painter->setPen(hilitePen);
painter->setBrush(Qt::NoBrush);
painter->drawRect(this->boundingRect().adjusted(pad, pad, -pad, -pad));
}
}

private:
qreal m_penWidth;
};

main.cpp

#include "MyRect.h"

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>

int main(int argc, char **argv)
{
QApplication app(argc, argv);
QGraphicsView view;

view.setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
view.setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );

QGraphicsScene *scene = new QGraphicsScene( 2147583648.0,22.0,
19900000.0,1536.0 );
view.setScene(scene);

QRectF seRect(2154794311.0,742.0,4000.0,5.0);
MyRect *rect = new MyRect;
scene->addItem( rect );
rect->setRect( seRect );

QRectF zmRect(2154696311.0,732.0,200000.0,20.0);

view.show();
view.fitInView( zmRect );

return app.exec();
}

wysota
13th February 2011, 08:13
I think you have a wrong assumption. If you increase the bounding rect by X then if you scale the item your X will be scaled as well. From what I see it is not what you want. Your current solution will not have a chance to work if you start scaling down the item. If you wish to continue this path then just invert the transformation of the painter and draw the hilight rect then.

mcarter
13th February 2011, 16:55
I agree . . . I am still trying to understand QGraphicsView/QGraphicsScene/QGraphicsItem interaction.

That was sort of my original question. I guess since the item can be drawn in many views, you cannot process the transform until the paint event. (?)

If I draw the hilite inside the paint event, will it not draw outside the boundingRect? Is it true that an item can only draw within its boundingRect? Can I adjust the boundingRect inside the paint event?

btw, is the pen size also based on scene scaling? So if I want to change the pen size to be larger for the rectangle draw I need to adjust based on transform to get the proper pixel width? Will there be an impact if I have 100,000's of these items on the screen?

Since I am new the this, is there a better way?

wysota
13th February 2011, 17:17
If I draw the hilite inside the paint event, will it not draw outside the boundingRect?
Physically it will be drawn but you'll get artifacts if you start manipulating your view.

Can I adjust the boundingRect inside the paint event?
No.


btw, is the pen size also based on scene scaling?
Unless you set it to cosmetic then yes.


So if I want to change the pen size to be larger for the rectangle draw I need to adjust based on transform to get the proper pixel width? Will there be an impact if I have 100,000's of these items on the screen?
I don't understand what you mean.


Since I am new the this, is there a better way?
Yes. Use a separate item was hilighting. Make it a child of your main item and adjust its geometry based on the geometry of the parent. You can even set the ItemIgnoresTransformations flag on it.

MarekR22
13th February 2011, 17:57
From my experience you should restore state of painter at the end of paint.
Without it you can have strange artifacts. Of course if you are using some optimization flag to auto store painter then this problem will not appear.
When you are writing own graphics item you shouldn't try to evaluate impact of transformation, especially when you have little experience with graphics freamework.

Try correct this code like that:

class MyRect : public QObject, public QGraphicsRectItem
{
Q_OBJECT

public:
MyRect( QGraphicsItem *pParent=0 )
: QGraphicsRectItem( pParent )
{
setItUp();
}
MyRect( const QRectF & rect, QGraphicsItem* pParent=0 )
: QGraphicsRectItem( rect, pParent )
{
setItUp();
}
~MyRect() {}

void setItUp()
{
m_penWidth = 1.0;
setPen( QPen(Qt::green) );
setAcceptHoverEvents( true );
setFlag( QGraphicsItem::ItemIsSelectable, true );
}

QRectF boundingRect() const
{ //looks ok
return this->rect().adjusted( -m_penWidth, -m_penWidth,
m_penWidth, m_penWidth );
}

protected:
void paint( QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget )
{
Q_UNUSED(widget);

// this way is faster then painter->store();
QPen oldPen = painter->pen();
QBrush oldoldBrush = painter->brush();

painter->setBrush(brush());
if (option->state & QStyle::State_Selected) {
painter->setPen(pen());
} else {
QPen hilitePen(QBrush(Qt::red),m_penWidth);
painter->setPen(hilitePen);
}
painter->drawRect(rect());

// restore painter:
painter->setPen(oldPen);
painter->setBrush(oldBrush);
}

private:
qreal m_penWidth;
};

Another thing why you are trying do it yourself?
Why not just manipulate properties of QGraphicsRectItem?
Just use QAbstractGraphicsShapeItem::setBrush and QAbstractGraphicsShapeItem::setPen when state of item is changed.
Re-implement itemChange method to react on that.

mcarter
13th February 2011, 22:08
So if I want to change the pen size to be larger for the rectangle draw I need to adjust based on transform to get the proper pixel width? Will there be an impact if I have 100,000's of these items on the screen?

I don't understand what you mean.

I was wondering if I start doing a lot of transform manipulation in the item, will this have an impact of I have many, many, many of these items in the view.


Since I am new the this, is there a better way?

Yes. Use a separate item was hilighting. Make it a child of your main item and adjust its geometry based on the geometry of the parent. You can even set the ItemIgnoresTransformations flag on it.

This sounds like the way to go, especially with the itemIgnoresTransformation. However, I am not sure how to implement. Do I override the itemChange of the main item to update the geometry of the child? Does the child get the actual transform of the scene/view thru its paintEvent so I can scale it properly? Just need a little push to get a better handle on GV.

Added after 6 minutes:

MarekR22, your answer is a valid response if I was only interested in changing the color of the item rectangle. I would indeed let the item do most of the work.

What I am looking for is a way to place a highlight rectangle around the entire item's rectangle.

wysota
13th February 2011, 22:13
I was wondering if I start doing a lot of transform manipulation in the item, will this have an impact of I have many, many, many of these items in the view.
Yes, of course.


This sounds like the way to go, especially with the itemIgnoresTransformation. However, I am not sure how to implement. Do I override the itemChange of the main item to update the geometry of the child?
Probably the other way would be better. It'd be best if the "parent" had a placeholder for the "hilight item" and called some function in the item when its boundingRect() changes.


Does the child get the actual transform of the scene/view thru its paintEvent so I can scale it properly? Just need a little push to get a better handle on GV.
The child only has to reread the parent's bounding rect and adjust itself to it. If you make the child a "resizer item" (with anchors that resize the main item when you drag them) it's even simpler as the child can then control the parent without any special treatment from the parent.

MarekR22
14th February 2011, 09:56
class MyRect : public QObject, public QGraphicsRectItem
{
// all other stuff without paint method
/// .....

protected:
QVariant itemChange ( GraphicsItemChange change, const QVariant & value )
{
if (change==ItemSelectedHasChanged) {
setPen(value.toBool()?mSelectedPen:mNormalPen);
}
return QGraphicsRectItem::itemChange(change, value);
}
private:
QPen mSelectedPen, mNormalPen;
};

mcarter
14th February 2011, 13:17
Probably the other way would be better. It'd be best if the "parent" had a placeholder for the "hilight item" and called some function in the item when its boundingRect() changes.

Well thats part of my GV ignorance, how can I determine when the boundingRect changes? Is there an event? Do I override setRect?


The child only has to reread the parent's bounding rect and adjust itself to it. If you make the child a "resizer item" (with anchors that resize the main item when you drag them) it's even simpler as the child can then control the parent without any special treatment from the parent.

This particular item does not need to be resized, just highlighted in some manner. However, I do need the resizer option with some other items . . . and I guess the same resizer child could be used for highlighting, just ignore the anchors.

So are there any code snippets handy to perform this task, to get me started.

wysota
14th February 2011, 17:50
Well thats part of my GV ignorance, how can I determine when the boundingRect changes? Is there an event? Do I override setRect?
The item usually knows it changes its own boundingRect() :)

mcarter
14th February 2011, 22:42
The item usually knows it changes its own boundingRect()

Well, if its based on its own rect, then not really . . . unless you override the setRect function (which is not a virtual function).


to get the child item working, do I hide/show based on the select state in the paint method of the parent?

wysota
14th February 2011, 23:10
You can call your own method that calls setRect(). setRect() won't be called behind your back, you know.

mcarter
14th February 2011, 23:53
duh . . . I have control of this item . . . way too many long hours . . . thanks, wysota

still would be interested if someone has a resizer item on hand :)

spud
15th February 2011, 17:53
Well, since you asked so nicely and I haven't posted anything here for ages...

Here's an old example I managed to dig up:
ResizeItem.h


class ResizeItem : public QGraphicsItemGroup
{
enum Handle{None = -1, TL, T, TR, R, BR, B, BL, L, NrOfHandles};
QGraphicsRectItem* handles[NrOfHandles];
Handle _selectedItem;
public:
ResizeItem()
: _selectedItem(None)
{
for(int i = 0;i<NrOfHandles;i++)
{
handles[i] = new QGraphicsRectItem(QRectF(-2,-2,5,5), this);
handles[i]->setFlags(ItemIgnoresTransformations);
handles[i]->setPen(Qt::NoPen);
handles[i]->setBrush(Qt::black);
}
handles[TL]->setCursor(QCursor(Qt::SizeFDiagCursor));
handles[T ]->setCursor(QCursor(Qt::SizeVerCursor));
handles[TR]->setCursor(QCursor(Qt::SizeBDiagCursor));
handles[R ]->setCursor(QCursor(Qt::SizeHorCursor));
handles[BR]->setCursor(QCursor(Qt::SizeFDiagCursor));
handles[B ]->setCursor(QCursor(Qt::SizeVerCursor));
handles[BL]->setCursor(QCursor(Qt::SizeBDiagCursor));
handles[L ]->setCursor(QCursor(Qt::SizeHorCursor));
hide();
}

protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemParentChange && scene())
{
QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(value.value<QGraphicsItem*>());
if(rectItem==0){
hide();
return 0;
}
repositionChildren(rectItem->rect());
show();
}
return QGraphicsItem::itemChange(change, value);
}

void repositionChildren(QRectF rect)
{
handles[TL]->setPos(rect.left(), rect.top());
handles[T ]->setPos(rect.center().x(), rect.top());
handles[TR]->setPos(rect.right(), rect.top());
handles[R ]->setPos(rect.right(), rect.center().y());
handles[BR]->setPos(rect.right(), rect.bottom());
handles[B ]->setPos(rect.center().x(), rect.bottom());
handles[BL]->setPos(rect.left(), rect.bottom());
handles[L ]->setPos(rect.left(), rect.center().y());
}

void mousePressEvent ( QGraphicsSceneMouseEvent * event )
{
// Find out which handle was clicked
int minDist = 10000000;
int closestHandle = None;
for(int i = 0;i<NrOfHandles;i++)
{
int dist = (event->pos()-handles[i]->pos()).manhattanLength();
if(dist<minDist)
{
closestHandle = i;
minDist = dist;
}
}
_selectedItem = (Handle)closestHandle;
event->accept();
}

void mouseMoveEvent ( QGraphicsSceneMouseEvent * event )
{
QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(parentItem());
if(rectItem==0)
return;

QRectF rect = rectItem->rect();
QPointF pos = event->scenePos()-scenePos();

if(_selectedItem==TR || _selectedItem==R || _selectedItem==BR)
rect.setRight(qMax(rect.left(), pos.x()));
else if(_selectedItem==TL || _selectedItem==L || _selectedItem==BL)
rect.setLeft(qMin(rect.right(), pos.x()));

if(_selectedItem==BL || _selectedItem==B || _selectedItem==BR)
rect.setBottom(qMax(rect.top(), pos.y()));
else if(_selectedItem==TL || _selectedItem==T || _selectedItem==TR)
rect.setTop(qMin(rect.bottom(), pos.y()));

rectItem->setRect(rect);
repositionChildren(rectItem->boundingRect());
}

void mouseReleaseEvent ( QGraphicsSceneMouseEvent * )
{
_selectedItem=None;
}
};
main.cpp


#include <QtGui>

// There is one global resize item, which stays hidden most of the time.
ResizeItem* resizeItem;

class RectItem : public QGraphicsRectItem
{
public:
RectItem(QRectF rect)
: QGraphicsRectItem(rect)
{
setFlags(ItemIsSelectable|ItemIsMovable);
setBrush((Qt::GlobalColor)((qrand()%14)+4));
}
QVariant itemChange(GraphicsItemChange change, const QVariant &value)
{
// When this item is selected, we grab the resizeItem by calling setParentItem(this)
if (change == ItemSelectedChange)
resizeItem->setParentItem(value.toBool() ? this : 0);
return QGraphicsItem::itemChange(change, value);
}
};

int main(int argc, char* argv[])
{
QApplication app(argc, argv);

QGraphicsScene scene;
scene.setSceneRect(0, 0, 600, 600);
scene.addItem(resizeItem = new ResizeItem);
for(int i=0;i<50;i++)
scene.addItem(new RectItem(QRectF(qrand()%500, qrand()%500, 100, 100)));

QGraphicsView view;
view.setScene(&scene);
view.show();

return app.exec();
}

The idea is that there is only one resize item which get's moved around whenever a rectangle is clicked. The resize item itself is invisible, but it has eight scale invariant children, which stay 5x5 pixel, regardless of view scale.

5958

d_stranz
15th February 2011, 22:09
Now that's a very clever example of "thinking outside the box".

mcarter
26th February 2011, 17:42
sorry about the late response, been off working other gui issues

spud, I appreciate the post . . . very cool
this has helped in understanding a little more about qgraphicsview and qgraphicsitems

mcarter
28th February 2011, 23:37
I see how the resize items work.

Now lets say i wanted the resizers to be the full-length of the rect I am adjusting.
Something like:

6003

I am trying to create an extraction item for a qgraphicsview that is showing some freq/time pixmaps. The cyan in the picture is where the mouse has entered the resizer edge. Both sides hilite because they would move in unison around the center line (adjusting bandwidth). The top and bottom would be adjusted individually for time.

Once I start resizing the edge items to match the center rect, that is where my problem starts . . . because I am not sure how to exactly go about doing that. Do I get the transformation from parent? Maybe I am just complicating the problem in trying to produce this version of an extraction window that a client has requested.