polygon QGraphicsItem shape



  • i have a QGraphicsItem sublcass where i add some members, call it GraphicsItem.
    then i derive from this class a Polygon class. notice i don't derive from both QGraphicsPolygonItem and GraphicsItem.

    in my Polygon class i reimplemented boundingRect() and paint():

    boundingRect():
        return the bounding rectangle for the polygon
    

    this works. but i also need to be able do things when the item is hovered, clicked, double-clicked.
    i have these in GraphicsItem class:

    	void hoverEnterEvent(QGraphicsSceneHoverEvent *event)
            {
    		prepareGeometryChange();
    		m_hovered = true;
    		QGraphicsItem::hoverEnterEvent(event);
            }
    
    	void hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
            {
    		prepareGeometryChange();
    		m_hovered = false;
    		QGraphicsItem::hoverLeaveEvent(event);
            }
    
    	void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
            {
    		qDebug() << "double clicked";
    		QGraphicsItem::hoverLeaveEvent(event);
            }
    

    and in Polygon class i do (for drawing outline when hovered):

    	void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
            {
    		auto pen_outline = QPen(QBrush(QColor(Qt.cyan)), 5);
    		auto pen_line = QPen(QBrush(QColor(Qt.red)), 3);
    
    		if (m_hovered)
                    {
    			painter->setPen(pen_outline);
    			painter->drawPolygon(QPolygonF(m_coords));
                    }
    
    		painter->setPen(pen_line);
    		painter->drawPolygon(QPolygonF(m_coords));
            }
    

    however, the outline isn't drawn and the "double clicked" isn't output.

    should i reimplement shape() too for this to work? and if so, how?



  • @user4592357
    Yes I think you should implement the shape function.
    Can you post all the code for these classes (at least enough to test it anyway)



  • Are you sure the boundingRect() implementation is correct, and uses the right coordinate system?

    The default shape() implementation would use the boundingRect(). So you need to reimplement shape() if you e.g. want to react only to double clicks into the polygon area. However, the default implementation should be sufficient that the doubleClick works in principle.



  • @kenchan here it is and sorry it's in python.

    the base class:

    class Item(QGraphicsItem):
    	def __init__(self, coords, parent=None):
    		super(Item, self).__init__(parent)
    
    		# set the properties
    		self.coords = coords
    		self._hovered = False
    		
    		self.setAcceptHoverEvents(True)
    		self.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemClipsToShape)
    
    	def hoverEnterEvent(self, event):
    		self.prepareGeometryChange()
    		self._hovered = True
    		super(Item, self).hoverEnterEvent(event)
    
    	def hoverLeaveEvent(self, event):
    		self.prepareGeometryChange()
    		self._hovered = False
    		super(Item, self).hoverLeaveEvent(event)
    
    	def mouseDoubleClickEvent(self, event):
    		print("dbl click")
    		super(Item, self).mouseDoubleClickEvent(event)
    

    the polygon class:

    class PolygonItem(ErroItem):
    	def __init__(self, coords, parent=None):
    		""" Initialize the polygon item. """
    		super(PolygonItem, self).__init__(coords, parent)
    
    	def boundingRect(self):
    		return bounding_rect(self.coords)
    
    	def paint(self, painter, option, widget):
    		""" Mandatory override of base class. """
    		pen_outline = QPen(QBrush(QColor(Qt.cyan)), 5, join=Qt.MiterJoin)
    		pen_line = QPen(QBrush(QColor(Qt.red)), 3, join=Qt.MiterJoin)
    
    		if self._hovered:
    			painter.setPen(pen_outline)
    			painter.drawPolygon(QPolygonF(self.coords))
    
    		painter.setPen(pen_line)
    		painter.drawPolygon(QPolygonF(self.coords))
    


  • @user4592357
    Thanks for posting the code As you probably guessed I don't use Qt python :-). I am sure there are python experts who can give you advice.
    I can consider its behaviour in terms of C++ though.



  • @kenchan
    yeah i mean there's nothing specific to python here



  • How is the function "bounding_rect(self.coords)" implemented?



  • @Asperamanca

        max_x = max_y = -inf
        min_x = min_y = inf
    
        # calculate min/max x/y values
        for point in points:
            if point.x() > max_x:
                max_x = point.x()
            elif point.x() < min_x:
                min_x = point.x()
            if point.y() > max_y:
                max_y = point.y()
            elif point.y() < min_y:
                min_y = point.y()
    
        # construct a rectangle out of those coordinates        
        return QRectF(QPointF(min_x, max_y), QPointF(max_x, min_y))
    


  • Not sure this is related to your issue, but is there any specific reason you flip the y axis?



  • @Asperamanca
    what do you mean by flip? for bounding rect i take top left and bottom right coordinates of the rect



  • @user4592357
    GraphicItem coordinates are origin at top left and positive axis down to the right. So your Y axis is flipped.
    Unless you changed it somewhere else.



  • @kenchan
    oh right, so i need to take bottom left and top right points?



  • @user4592357
    no, top left and bottom right is probably what you need

    QRectF(QPointF(min_x, min_y), QPointF(max_x, max_y))
    

    http://doc.qt.io/qt-5/qgraphicsitem.html#details



  • @kenchan
    that's what i was saying





  • @user4592357 said in polygon QGraphicsItem shape:

    Not sure that came across:
    The way you posted your code, you actually use bottom/left and top/right to construct the rectangle.

    return QRectF(QPointF(min_x, max_y), QPointF(max_x, min_y))



  • @kenchan
    it's really weird. i set background brush to black of my graphics view, and when i add something like

    item = PolygonItem([QPointF(30, 30), QPointF(50, 50), QPointF(100, 100)])
    

    to the scene, the view becomes white and no item is drawn :(

    here's the scene and view:

    class Scene(QGraphicsScene):
        def __init__(self, parent=None):
            super(Scene, self).__init__(parent)
    
    class View(QGraphicsView):
        def __init__(self, parent=None):
            super(View, self).__init__(parent)
            self.setRenderHint(QPainter.Antialiasing)
            self.setRenderHint(QPainter.TextAntialiasing)
            self.setSceneRect(self.scene().sceneRect())
            self.setBackgroundBrush(QBrush(Qt.black))
    

    this is how i create the scene and view in main window:

    	def __create_scene_and_view(self):
                    self.__scene = Scene(self)
    		self.__view = View(self.__scene)
    		self.__layout_view.show()
    		self.setCentralWidget(self.__view)
    

    some items are drawn correctly, though. for example:

    item = PolygonItem([QPointF(75, -10), QPointF(60, 40), QPointF(110, 10), QPointF(0, 0)])
    

    i noticed, if i add QPointf(0, 0) to the first (non-working) item, it is drawn too



  • @kenchan
    hi,

    i fixed the above issue (related to bounding box)

    this is the bound rect now:

    		bb = brect(self.coords)
    
    		if self._hovered:
    			bb.setX(bb.x() - 4)
    			bb.setY(bb.y() - 4)
    			bb.setWidth(bb.width() + 4)
    			bb.setHeight(bb.height() + 4)
    
    		return bb
    

    and this is paint():

    	def paint(self, painter, option, widget):
    		pen_outline = QPen(QBrush(QColor(Qt.cyan)), 5, join=Qt.MiterJoin)
    		pen_line = QPen(QBrush(QColor(Qt.red)), 3, join=Qt.MiterJoin)
    
    		if self._hovered:
    			painter.setPen(pen_outline)
    			painter.drawPolygon(QPolygonF(self.coords))
    
    		painter.setPen(pen_line)
    		painter.drawPolygon(QPolygonF(self.coords))
    

    and these are the results when normal:
    alt text

    and when hovering:
    alt text

    as you can see, the red rectangle has become a lot thicker. why is that?

    i think one reason may be that i'm using coordinates for both normal and highlighted drawing?

    by the way, if i remove the if in bounding rect, i get the "outline" drawn, only within the rect but i need outside it.



  • @user4592357 Hello again,
    I can't see why the red box should get thicker with that code.
    You should probably only offset your bounding box position by -2 if you increase the width and height by 4 (4/2=2 extra pixels on each side).



  • @kenchan
    i have played with numbers. setting that to -2 looks worse

    if i remove the if in bounding rect, i get the "outline" drawn, only within the rect but i need outside it.



  • @user4592357
    since you have a width of 3 and 5. assuming the thicker red line in the lower image is width 3 the cyan line looks correct at width 5 having 1 pixel on each side. So, I am thinking that the upper thinner rectangle is incorrectly drawn. It does not look like it has a width of 3 to me. I cannot see why that is though.
    BTW it made a similar one with C++ and it looks fine.



  • @kenchan
    red is probably 3, because cyan is 5. but i remove the if from boundingRect()

    now, when both are drawn from same (x, y), since cyan is 5, is appears to be inside the rect.
    when i do this, however, the item "goes up" everytime it is hovered:

    	def paint(self, painter, option, widget):
    		# create the pens
    		pen_outline = QPen(QBrush(QColor(Qt.cyan)), 5, join=Qt.MiterJoin)
    		pen = QPen(QBrush(QColor(Qt.red)), 3, join=Qt.MiterJoin)
    
    		if self._hovered:
    			painter.setPen(pen_outline)
    			coords = []
    			for coord in self.coords:
    				coord.setX(coord.x() - 2)
    				coord.setY(coord.y() - 2)
    			 	coords.append(coord)
    			painter.drawPolygon(QPolygonF(coords))
    
    		painter.setPen(pen)
    		painter.drawPolygon(QPolygonF(self.coords))
    

    i need to do something like this i guess - draw a bigger polygon under the original one.
    but how can i do it, having the coordinates?



  • @user4592357

    your bounding rect should have either the small rect (when not hovering) or the large rect (when hovering). It might work fine with only the large rect though.
    My bounding rect looks like this, it works fine.

    QRectF TestItem2::boundingRect() const
    {
        if(hovering)
            return QRectF(-2, -2, 104, 104);
        else
            return QRectF(-1,-1, 102, 102);
    }
    

    my paint function looks like this

    void TestItem2::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        QPen pen5(QBrush(QColor(0,255,255)),5,Qt::SolidLine,Qt::SquareCap,Qt::MiterJoin);
        QPen pen3(QBrush(QColor(255,0,0)),3,Qt::SolidLine,Qt::SquareCap,Qt::MiterJoin);
        const QPointF points[4] = {QPointF(0.0,0.0),QPointF(100.0,0.0),QPointF(100.0,100.0),QPointF(0.0,100.0)};
        if(hovering)
        {
            painter->setPen(pen5);
            painter->drawPolygon(points,4);
        }
        painter->setPen(pen3);
        painter->drawPolygon(points,4);
    }
    

    not quite the same as yours but close enough.
    I see you have QGraphicsItem.ItemClipsToShape set, that might cause you problems if you don't have your shape function working correctly. Assuming you do have a shape function defined that is.
    here is what my box looks like



  • @kenchan
    yeah it was that... ugh, thanks.

    by the way how can i make the outline don't appear "inside"?



  • @user4592357
    You want the highlight outline not to show inside the red box?



  • @kenchan
    yes



  • @user4592357
    hmm. easiest way with a simple rectangle is to just draw a line of width 1 around the outside of your red one.
    Same thing if it is more complex polygon but tricky to offset the points in the correct direction.
    alt text



  • @kenchan
    that's actually what i wanted to do - but the problem is i don't know when to add and when to subtract from coordinate:

    	def paint(self, painter, option, widget):
    		""" Mandatory override of base class. """
    
    		# create the pens
    		pen_outline = QPen(QBrush(QColor(Qt.cyan)), 1, join=Qt.MiterJoin)
    		pen = QPen(QBrush(QColor(Qt.red)), 2, join=Qt.MiterJoin)
    
    		if self._hovered:
    			painter.setPen(pen_outline)
    			
    			for pt in range(len(self.coords)):
    				coord1 = self.coords[pt % len(self.coords)] - QPointF(1, 1)
    				coord2 = self.coords[(pt + 1) % len(self.coords)] - QPointF(1, 1)
    				painter.drawLine(coord1, coord2)
    			# painter.drawPolygon(QPolygonF(self.coords))
    
    		painter.setPen(pen)
    		painter.drawPolygon(QPolygonF(self.coords))
    

    result:
    alt text



  • @user4592357
    ah yes, that is the tricky bit :-). you want the point that is 1 pixel beyond the end of each line segment and offset 1 pixel in the normal direction to that line segment, assuming your lines segments progress in the same orderly direction around your polygon. Think vectors... there are several ways to think about it in terms of vectors. The outward pointing average normal vector to the line segments that meet at the corner, etc.



  • @kenchan
    oh sorry, but i don't get what you mean



  • @user4592357
    Well, you must devise an algorithm to determine how to offset of the polygon outwards by one pixel. I come from a CAD/CG background so I think about that in term of direction vectors etc. Sorry but I don't have time right now to do that for you, you should probably do that yourself :-). Since we are only talking about one pixel offset I could imagine a simple lookup table where you select how to offset the pixel depending on the direction of your line segments in the local coordinate system of the polygon i.e. coordinates increase from top left down to bottom right... your line segments are pointing in some direction in relation to that coordinate system. But this kind of thing has nothing to do with Qt because you know how to draw it with Qt once you have decided which pixel to use :-).



  • @kenchan
    okay, at first i didn't get that i needed to consider the direction of the vector.

    and by the way, i have reimplemented view's wheelEvent but i also need scrollbars to appear when needed:

    	def wheelEvent(self, event):
    		factor = 1.41 ** (event.angleDelta().y() / 240.0) # ** is pow
    		self.scale(factor, factor)
                    # call base version
    		super(View, self).wheelEvent(event)
    

    now scrollbars only appear when, say i have a few items, when i scroll enough and they won't fit in view, only then. i think it's because i set view's scene rect to scene's rect?

    this is view setup:

    	def __init__(self, parent=None):
    		super(View, self).__init__(parent)
    		self.setDragMode(QGraphicsView.NoDrag)
    		self.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing)
    		self.setSceneRect(self.scene().sceneRect())
    		self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
    		self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
    


  • @user4592357
    you have the scroll bar policy set as Qt.ScrollBarAsNeeded so that is what it is doing for you.
    Is that not what you wanted?



  • @kenchan
    even if i do scroll bars always on, they don't appear when i scroll the view.

    say i have the window, where i have small items only in the center of the scene. now i do "wheel event" a little to zoom into the view. view is zoomed in. since the items still fit in the view, scroll bars don't appear but i want them to.

    now the scroll bars only appear when the items dont fit in view



  • @user4592357
    in that case I guess you must turn them on and off when you decide you want them. Changing the scrollbar policy when you need to.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.