0

enter image description here

In my view there are 3 lines. 1 Polyline, and 2 straight lines. I have named them as P1 and s1 and s2

I am not able to select s1 line. If I try to select it , P1 gets selected ( though I have not clicked on P1 )

Selection of p1 is also not sharp. If I clicked somewhere around P1 ( not on P1 ) still P1 gets selected.

 void Widget::on_designButoon_clicked()
  {
      // For straight line S2
      QPolygon net0;
      net0 << QPoint(50,180);
      net0 << QPoint(600,180);
      MyPoly* _poly0 = new MyPoly();
      _poly0->DrawPolyline(net0,scene);
      scene->addItem(static_cast<QGraphicsPathItem*>(_poly0));
            // Same logic for Line S1 and P1
        }
    

MyPoly.h

class MyPoly : public QGraphicsPathItem
{
    //Q_OBJECT
public:
    explicit MyPoly();
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
    void DrawPolyline(QPolygon polygon);
private:
 QPolygon polygon_;
};

MyPoly.cpp

MyPoly::MyPoly()
{}
void MyPoly::DrawPolyline(QPolygon polygon)
{
    this->polygon_ = polygon;
    QPainterPath pPath;
    pPath.addPolygon(polygon);
    this->setPen(QPen(QColor("blue"), 2));
    this->setPath(pPath);
    this->setFlag(QGraphicsItem::ItemIsSelectable);
}

void MyPoly::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                             QWidget *widget)
{
    auto copied_option = *option;
    copied_option.state &= ~QStyle::State_Selected;
    auto selected = option->state & QStyle::State_Selected;
    QGraphicsPathItem::paint(painter, &copied_option, widget);
    if (selected) {
        painter->save();
        painter->setBrush(Qt::NoBrush);
        painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine));
        if(contains(currentCursorPos))
                 painter->drawPath(shape());
        painter->restore();
    }
}

bool MyPoly::contains(const QPointF &point) const
{
    for(int i=0;i < polygon_.count() - 1; i++)
    {
        QPointF firstPoint = polygon_.at(i);
        QPointF lastPoint = polygon_.at(i+1);

        if(firstPoint.x() == lastPoint.x())
        {
            qDebug()<<"Inside vertical line ";
            //It is a vertical line
            if(firstPoint.x() == point.x() &&
                    point.y() >= firstPoint.y() && point.y() <= lastPoint.y())
                return true;
        }
        else
        {
            // it is a horizontal line
            if(point.x() >= firstPoint.x() && point.x() <= lastPoint.x() &&
                    (firstPoint.y() - 3 <= point.y()) && (point.y() <= lastPoint.y() + 3 ))
                return true;
        }
    }
    return false;
 }
tushar
  • 313
  • 4
  • 10

1 Answers1

0

From looking at the Qt source code it appears that QGraphicsView determines which QGraphicsItem(s) are located at the clicked-on point by calling their contains() method.

Assuming you haven't re-implemented MyPoly::contains(const QPointF &) const to do something more specific, that means that Qt is calling the default QGraphicsItem::contains(const QPointF &) const method, which is implemented like this:

bool QGraphicsItem::contains(const QPointF &point) const
{
   return isClipped() ? clipPath().contains(point) : shape().contains(point);
}

... which in turn calls QPainterPath::contains(const QPointF &) const, which uses either the Non-zero winding rule or the Even-Odd rule (depending on the QPainterPath's fillRule setting) to determine if the point is in the path. In either case it looks like the algorithm is designed to detect whether the point is in the interior of a polygon defined by the path and not just "directly on the line", so if you want a more accurate algorithm for your use-case, you'll probably want to override the contains method of your subclass and implement some more precise logic there.

Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
  • Btw, I bet if you temporarily comment out the `painter->setBrush(Qt::NoBrush)` line from your `paint()` method, then Qt's on-mouse-click behavior will make more intuitive sense relative to what's drawn on the screen. – Jeremy Friesner May 27 '22 at 05:26
  • Sorry but I removed `painter->setBrush(Qt::NoBrush)` from `paint()` but it did not work. Problem is still there. But anyway I understood what you are trying to say. I need to override `contains()` from `QGraphicsItem`. I will let you know once it is done. Thank you. – tushar May 27 '22 at 05:52
  • I was overriding `contains()` as per your suggestion but while reading I understood that, instead of `contains()` I should override `shape()`. So I tried but I could not. So I created a separate thread for it. Can you look into it ? https://stackoverflow.com/questions/72407209/how-to-minimize-bounding-rect-in-qt – tushar May 28 '22 at 09:39
  • 1
    I don’t think overriding shape() can help, since shape() returns a QPainterPath, and the documentation for QPainterPath::contains() says: Set operations on paths will treat the paths as areas. Non-closed paths will be treated as implicitly closed. – Jeremy Friesner May 28 '22 at 13:43
  • Sorry but it is quite difficult for me to override `contains()`. I am clueless about how to proceed. It would be great if you could help me. – tushar May 28 '22 at 14:48
  • 1
    You need to write code so that `contains()` returns true if the mouse-click location is on one of the lines, or false otherwise. If the line you want to check against is horizontal, then test if the mouse-click's `y` location is equal to the line's `y` value (or perhaps within a few pixels of it, to allow for user-slop), and if the mouse-click's `x` location is between the line's two endpoint `x` values. Checking against a vertical line is similar. If you need to check against non-horizontal/non-vertical lines, there is a mathematical formula to do that, but you'll have to look it up. – Jeremy Friesner May 28 '22 at 19:41
  • While overriding `contains()` I observed following : Addition sequence of lines into scene , matters a lot. `If I (first) add P1 -> S1 -> (last) S2 -------- everything works perfectly` . `If I (first) add S2 -> S1 -> (last) P1 -------- S2 Can not be selected`. `If I (first) add S1 -> S2 -> (last) P1 ---------- S1 can not be selected` Why ? And how to tackle ? – tushar May 29 '22 at 19:29
  • I have overridden `contains()`. Thank you for your suggestions. – tushar May 29 '22 at 19:46
  • Does contains() get called on all three items, or does Qt stop testing after one of the contains() calls returns true? If the latter, then it’s probably a case of one of the shapes “blocking” another by having contains() return true when it shouldnt – Jeremy Friesner May 29 '22 at 20:08
  • yes. Here one of the shapes blocking others. While selecting any of the overlapping lines, the one with bounding rect on top is selected. How to solve this problem ? – tushar May 29 '22 at 22:46
  • I tried this link I override `shape()` https://stackoverflow.com/questions/26271623/customizing-shape-of-bounding-rect But in the answer they have consider single line but I have `polygon`. So some option did not work. For one of the option, it created bounding rect and showing it when I was clicking on line.. Why it is showing bounding rect ? Means I am confused. Can you help me in overridding `shape()` ( If I need to override `shape()` ) – tushar May 29 '22 at 23:04
  • I found the exact problem. I am creating polyline using QPolygonF. Internally , it makes it closed by connecting start point with end point. Now in this closed area, if I click, it selectes only polyline P1. How to tackle this issue ? I have made another thread for this. Please look into it. https://stackoverflow.com/questions/72431179/qpolygonf-internally-drawing-a-closed-polygon-which-is-troubling-while-selecting – tushar May 30 '22 at 08:23
  • I'll say it again: You need to write code so that contains() returns true if the mouse-click location is on one of the lines, or false otherwise. – Jeremy Friesner May 30 '22 at 13:17
  • Yes, I know. So I am trying over `contains()`. But in `contains()` I can not get all the 3 polygon. Whose bounding region I am clicking, that polygon I am getting. Not all 3 polygon. So If I click on P1's bounding region, then I get P1 polygon , and through line's logic which you had mentioned earlier I dont select that P1 because click is not on P1. Till this perfect but I dont get remaining 2 polygon. That's the problem. How to tackle this ? – tushar May 30 '22 at 15:07
  • I have updated my code. – tushar May 30 '22 at 15:16
  • It worked ! Finally it is working. `contains()` was not getting called for all polygon so I simply iterated over every item from `scene` and use `if(currentItem->contains(currentItem->mapFromScene(mousePoint))` and I got every polygon from `scene` and then logic in `contains()` did everything. Actually I had a pressure to think differently and not to stick to only one solution but I believed in you and sticked to `contains()` and I WON ! Thank you for your patience ! – tushar May 31 '22 at 12:54