QGraphicsItem hit detection problem.



  • I am having an issue where my QGraphicsItem is not alway performing proper hit detection with the mouse. I have subclassed QGrahicsItem and have overridden the shape() method. My shape() method calculates a polygon that surrounds the line. my boudingRect() function calculates a box that completely encloses the line. I have attached two screenshots. The first show the line highlighted in blue, indicating the hover event was fired. The second show the mouse moved just slightly to the right, but still well within the shape() and boundingRect() but the hover event did not fire. Any ideas on what I'm doing wrong?

    I should also note that if I move the endpoints of the line, the hit detection usually starts working normally. It seems somewhat random as to when it breaks.

    Note: The polygon surrounding the line is the shape() and the rectangle around that is the boundingRect()

    !http://i.stack.imgur.com/sfiik.png(Hover working)!
    !http://i.stack.imgur.com/Zy1Wt.png(Hover not working)!



  • What is the result if you perform a hit detection yourself in the mouseMove event?

    And, to check the obvious, you have called setAcceptHoverEvents(true) for your item?



  • Yes, setAcceptHoverEvents is true. The hover hit detection works about 80% of the time. It's the random 20% that confuses me.



  • Do you get the same 20 % when you do the hit detection yourself using shape().contains(mouseEvent->pos()) in the mouseMove event?



  • I added this:
    @void Connection::mouseMoveEvent(QGraphicsSceneMouseEvent *e) {
    if(shape().contains(e->pos()))
    int i = 0;
    }@

    I put a break point at the if statement and it never triggers. It doesnt even trigger when the hover event is working.

    Hover events for reference:
    @void Connection::hoverEnterEvent(QGraphicsSceneHoverEvent *e) {
    m_is_hovering = true;
    update();
    }

    void Connection::hoverLeaveEvent(QGraphicsSceneHoverEvent *e) {
    m_is_hovering = false;
    update();
    }@

    shape and boundingRect for reference:

    @QRectF Connection::boundingRect() const {
    QRectF rect = shape().boundingRect();
    return rect;
    }

    QPainterPath Connection::shape() const {
    QPainterPath path;

    QPointF endpoints[2];
    endpoints[0].setX(0);
    endpoints[0].setY(0);    
    endpoints[1].setX(0);
    endpoints[1].setY(0);
    
    if(m_terms[0] != NULL)
        endpoints[0] = m_terms[0]->scenePos();
    
    if(m_terms[1] == NULL)
        endpoints[1] = *m_end_point;
    else
        endpoints[1] = m_terms[1]->scenePos();
    
    QPointF left_point;
    QPointF right_point;
    
    bool line_is_positive = false;
    // Determine orientation of line
    if(endpoints[0].x() <= endpoints[1].x()) { // 0 is left most point
        left_point = endpoints[0];
        right_point = endpoints[1];
    } else if(endpoints[0].x() > endpoints[1].x()) { // 1 is left most point
        left_point = endpoints[1];
        right_point = endpoints[0];
    } 
    
    if(left_point.y() > right_point.y())
        line_is_positive = true;
    
    QVector<QPointF> points;
    qreal padding = 6.0;
    if(line_is_positive) {
        points.push_back(QPointF(left_point.x() - padding, left_point.y() - padding));
        points.push_back(QPointF(left_point.x() + padding, left_point.y() + padding));
        points.push_back(QPointF(right_point.x() + padding, right_point.y() + padding));
        points.push_back(QPointF(right_point.x() - padding, right_point.y() - padding));
        points.push_back(QPointF(left_point.x() - padding, left_point.y() - padding));
    } else {
        points.push_back(QPointF(left_point.x() - padding, left_point.y() + padding));
        points.push_back(QPointF(left_point.x() + padding, left_point.y() - padding));
        points.push_back(QPointF(right_point.x() + padding, right_point.y() - padding));
        points.push_back(QPointF(right_point.x() - padding, right_point.y() + padding));
        points.push_back(QPointF(left_point.x() - padding, left_point.y() + padding));
    }
    path.addPolygon(QPolygonF(points));
    return path;
    

    }@



  • My mistake, an item does not normally receive MouseMove events (it receives them only following a MousePressEvent, under conditions specified there).

    For testing purposes, it might serve to put the hit detecting into a mousePress or keyPress event, then trigger that event in cases where the hover hit detection fails (but should not).



  • I put the check inside the mousePress event. When the line is not hovering, it is also not recieving the mousePress event. Qt thinks that the mouse is not inside the objects shape. However, I'm still drawing the shape and it is clearly correct. :/



  • I think the problem is with the boundingRect(). If I understand correctly, Qt is constantly polling the boundingRect() of all of the objects. Then if the mouse is within a boundingRect(), it then polls shape() of that object.

    I placed a breakpoint in shape(). It does not get called until the mouse enters the boundingRect(). When the hover is not working, the mouse is well within the boundingRect(), but shape is not called.



  • Does the boundingRect() need to have a specific orientation?



  • I recently encountered the same issue. I didn't know that the boundingRect() was used to trivially reject items, but it makes perfect sense. However, if you don't account for line thickness when computing the boundingRect, then it will have area = 0 when lines/curves are horizontal or vertical and this will cause your QGraphicsItem to be trivially rejected before shape() is hit tested.

    The solution for me was to implement the shape() function to include thickness plus any additional padding if necessary to make hovering/selection easier (using QPainterPathStroker). Then the boundingRect() implementation just returns shape().boundingRect().


Log in to reply
 

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