Walletfox.com

QGraphicsLineItem with a minimal selection box


This article presents an implementation of QGraphicsLineItem with a minimal selection box. This selection box closely follows the shape of the line item, yet enables easy selection and manipulation. Such an implementation enables better differentiation among items that require selection but happen to be close to one another.

Please note that this implementation is not suitable for collision detection. It makes the item appear larger than it is and as a result, it detects the collision with another item sooner than it should. However, if you require only easier manipulation of the QGraphicsLineItem, this implementation will work just fine.

Implementation details

The situation is illustrated in the figure below. In the original implementation of the QGraphicsLineItem, the original selection box coincides with the original bounding rectangle. In the new implementation, the selection box (selection polygon) follows the inclination of the line item. The size of the selection box is governed by the selection offset.

The original and new bounding rectangle

The header file below reflects these requirements. The new class CustomLineItem inherits from QGraphicsLineItem and introduces two member variables, one that governs the proportions of the selection box (qreal selectionOffset) and one that stores the selection polygon (QPolygonF selectionPolygon). Moreover, it overrides several methods that have to do with the introduction of the new selection box. These methods include public methods shape(), boundingRect() and paint() and a private method createSelectionPolygon().

class CustomLineItem : public QGraphicsLineItem
{
public:
    CustomLineItem(QLineF line, QGraphicsItem* parent = 0);
    QRectF boundingRect() const;
    QPainterPath shape() const;
    void paint (QPainter* painter, const QStyleOptionGraphicsItem* option,
                QWidget* widget = 0);
private:
    const qreal selectionOffset;
    QPolygonF selectionPolygon;
    void createSelectionPolygon();
};

In the following, I am going to introduce the implementation details of the CustomLineItem class that can be found in the source file customlineitem.cpp.

The implementation details of the constructor and createSelectionPolygon()

In the constructor we make the item selectable and movable and set the initial value of the selectionOffset to 20.0. We also call the method createSelectionPolygon() that constructs the selectionPolygon. The method createSelectionPolygon() constructs the selection polygon with the help of the line's angle (alpha) and the selectionOffset.

The figure below illustrates how the points of the selectionPolygon are computed. The angle of the line provided by Qt needs to be converted into radians. After that, we compute the offset (dx,dy) with the help of the line's angle and the selectionOffset. This offset(dx,dy) is going to bring us from the starting point of the line (p1) to the defining points of the selectionPolygon (o1, o2, o3, o4). The sin and cos functions already reflect the various possible line inclinations (ascending, descending).

Detail of the computation of the selection polygon
CustomLineItem::CustomLineItem(QLineF line, QGraphicsItem *parent):
    QGraphicsLineItem(line, parent), selectionOffset(20)
{
    setFlags(QGraphicsItem::ItemIsSelectable
             |QGraphicsItem::ItemIsMovable);
    createSelectionPolygon();
}

void CustomLineItem::createSelectionPolygon(){
    QPolygonF nPolygon;
    qreal radAngle = line().angle()* M_PI / 180;
    qreal dx = selectionOffset * sin(radAngle);
    qreal dy = selectionOffset * cos(radAngle);
    QPointF offset1 = QPointF(dx, dy);
    QPointF offset2 = QPointF(-dx, -dy);
    nPolygon << line().p1() + offset1
             << line().p1() + offset2
             << line().p2() + offset2
             << line().p2() + offset1;
    selectionPolygon = nPolygon;
    update();
}

The boundingRect(), shape() and paint() methods

Several other methods need to be overridden as a result of the new selection box. Firstly, we need to override the boundingRect(), otherwise the extra area introduced by the new selection box won't be painted properly. Secondly, the shape() needs to return the path of the new selection polygon. Last, the paint() method needs to be adjusted in order to draw the new polygon of a selected item. This can be seen below.

QRectF CustomLineItem::boundingRect() const {
    return selectionPolygon.boundingRect();
}

QPainterPath CustomLineItem::shape() const{
    QPainterPath ret;
    ret.addPolygon(selectionPolygon);
    return ret;
}

void CustomLineItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                           QWidget *widget){
    Q_UNUSED(option);
    Q_UNUSED(widget);
    painter->setPen(pen());
    painter->drawLine(line());
    if (isSelected()) {
        painter->setPen(QPen(Qt::black, 2, Qt::DashLine));
        painter->drawPolygon(selectionPolygon);
    }
}

Tagged: Qt