Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QGraphicsItem receive mouse events based on bounding rect, not item shape
Forum Updated to NodeBB v4.3 + New Features

QGraphicsItem receive mouse events based on bounding rect, not item shape

Scheduled Pinned Locked Moved Solved General and Desktop
9 Posts 2 Posters 1.6k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • E Offline
    E Offline
    Ewan Green
    wrote on last edited by
    #1

    I'm in a situation where I need to be able to select an item based on its geometry, not its visible shape. I have my items laid out in a grid, in a fashion where they would never overlap.
    I am using mouse press events to determine single-item selection, but it's as if my items aren't receiving mouse events that happen in their bounding rectangle, only their visible shape.
    I need it to function the same way the containing view's rubber band does when its selection mode is set to Qt::IntersectsItemBoundingRect.

    Ewan Green

    JonBJ 1 Reply Last reply
    0
    • E Ewan Green

      I'm in a situation where I need to be able to select an item based on its geometry, not its visible shape. I have my items laid out in a grid, in a fashion where they would never overlap.
      I am using mouse press events to determine single-item selection, but it's as if my items aren't receiving mouse events that happen in their bounding rectangle, only their visible shape.
      I need it to function the same way the containing view's rubber band does when its selection mode is set to Qt::IntersectsItemBoundingRect.

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by
      #2

      @Ewan-Green
      Unless someone answers better. These are only suggestions from me, I have no evidence for their efficacy!

      The only reference I could find to same/similar to yours is https://stackoverflow.com/questions/44621746/qt-check-if-mouse-in-bounding-rect-of-qgraphicsitem. That went without any answers, which is often indicative that nobody knows!

      Did you look at what each of boundingRegion(), boundingRect() & shape() return for your QGraphicsItems? Although documentation talks about repaint & collision detection for these, worth testing whether they have any effect on mouse events? For example, maybe shape() would be better returning a rectangle than your actual shape?

      Otherwise maybe you can implement what you need by intercepting QGraphicsView/Scene mouse events and doing your own "intersects" instead of those on the QGraphiceItems themsleves?

      Finally, if you say view's rubber band does the right thing, you could always look at its code for some ideas....!

      1 Reply Last reply
      2
      • E Offline
        E Offline
        Ewan Green
        wrote on last edited by Ewan Green
        #3

        Thank you! I've devised this based on what you've said.

        void AbstractTileView::mousePressEvent(QMouseEvent *evt) {
            for (int i = 0; i < items().size(); i++) {
              QGraphicsItem *item = items().at(i);
              if (item->boundingRegion(item->sceneTransform()).contains(evt->pos())) {
                item->setSelected(!item->isSelected());
                return;  // This can happen twice probably cuz of float rounding nonsense
              }
            }
        }
        

        It does exactly what I want, but I'm concerned that iterating over every single item in the scene every mouse press event is expensive & unnecessary. How can I make this better?

        Ewan Green

        JonBJ 1 Reply Last reply
        0
        • E Ewan Green

          Thank you! I've devised this based on what you've said.

          void AbstractTileView::mousePressEvent(QMouseEvent *evt) {
              for (int i = 0; i < items().size(); i++) {
                QGraphicsItem *item = items().at(i);
                if (item->boundingRegion(item->sceneTransform()).contains(evt->pos())) {
                  item->setSelected(!item->isSelected());
                  return;  // This can happen twice probably cuz of float rounding nonsense
                }
              }
          }
          

          It does exactly what I want, but I'm concerned that iterating over every single item in the scene every mouse press event is expensive & unnecessary. How can I make this better?

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote on last edited by JonB
          #4

          @Ewan-Green
          Can you use one of the QGraphicsScene::items() overloads to map the mouse position to your QGraphicsItem(s) there? I think the point is that is fast.

          QGraphicsScene uses an indexing algorithm to manage the location of items efficiently.

          There are Qt::IntersectsItemBoundingRect or Qt::IntersectsItemShape variants. If it's not accurate for you, you could use the const QRectF &rect overload to match against an area around the mouse point, at least to narrow down which QGraphicsItems before applying your test above.

          1 Reply Last reply
          1
          • E Offline
            E Offline
            Ewan Green
            wrote on last edited by Ewan Green
            #5

            Thank you. I've improved the function using the items() indexing. It looks like this:

              if (evt->button() == Qt::LeftButton) {
                QList<QGraphicsItem *> items =
                    this->items(QRect(evt->pos().x() - 10, evt->pos().y() - 10,
                                      evt->pos().x() + 10, evt->pos().y() + 10),
                                Qt::IntersectsItemBoundingRect);
                QListIterator<QGraphicsItem *> it(items);
                while (it.hasNext()) {
                  QGraphicsItem *item = it.next();
                  if (item->boundingRegion(item->sceneTransform()).contains(evt->pos())) {
                    item->setSelected(!item->isSelected());
                    return;  // This can happen twice cuz of float rounding nonsense
                  }
                }
            

            Now my question is, how can I have my overridden mousePressEvent() and still have the rubber band? I don't know of a way to do this without overriding the drag events as well. In this situation I would usually call the method of the parent class, but it's a protected member...

            Ewan Green

            JonBJ 1 Reply Last reply
            0
            • E Ewan Green

              Thank you. I've improved the function using the items() indexing. It looks like this:

                if (evt->button() == Qt::LeftButton) {
                  QList<QGraphicsItem *> items =
                      this->items(QRect(evt->pos().x() - 10, evt->pos().y() - 10,
                                        evt->pos().x() + 10, evt->pos().y() + 10),
                                  Qt::IntersectsItemBoundingRect);
                  QListIterator<QGraphicsItem *> it(items);
                  while (it.hasNext()) {
                    QGraphicsItem *item = it.next();
                    if (item->boundingRegion(item->sceneTransform()).contains(evt->pos())) {
                      item->setSelected(!item->isSelected());
                      return;  // This can happen twice cuz of float rounding nonsense
                    }
                  }
              

              Now my question is, how can I have my overridden mousePressEvent() and still have the rubber band? I don't know of a way to do this without overriding the drag events as well. In this situation I would usually call the method of the parent class, but it's a protected member...

              JonBJ Offline
              JonBJ Offline
              JonB
              wrote on last edited by
              #6

              @Ewan-Green said in QGraphicsItem receive mouse events based on bounding rect, not item shape:

              In this situation I would usually call the method of the parent class, but it's a protected member...

              I don't know about whatever the rubber band does. But what method of what parent's class are you talking about?

              1 Reply Last reply
              0
              • E Offline
                E Offline
                Ewan Green
                wrote on last edited by
                #7

                My class inherits QGraphicsScene. The QGraphicsScene mousePressEvent() surely has something to allow for the generation of the rubber band, which is why I want to call it.

                Ewan Green

                JonBJ 1 Reply Last reply
                0
                • E Ewan Green

                  My class inherits QGraphicsScene. The QGraphicsScene mousePressEvent() surely has something to allow for the generation of the rubber band, which is why I want to call it.

                  JonBJ Offline
                  JonBJ Offline
                  JonB
                  wrote on last edited by JonB
                  #8

                  @Ewan-Green said in QGraphicsItem receive mouse events based on bounding rect, not item shape:

                  My class inherits QGraphicsScene

                  So whatever it is you are looking for, if it's a protected QGrapicsScene::... method then you can access/call it....?

                  1 Reply Last reply
                  0
                  • E Offline
                    E Offline
                    Ewan Green
                    wrote on last edited by Ewan Green
                    #9

                    My bad, I was actually using QGraphicsView as a base. I've instead made a custom QGraphicsScene and I've overridden the mouse press event, like so:

                    void CustomGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *evt) {
                      switch (evt->button()) {
                        case Qt::LeftButton: {
                          QList<QGraphicsItem *> items =
                              this->items(evt->pos(), Qt::IntersectsItemBoundingRect);
                          QListIterator<QGraphicsItem *> it(items);
                          while (it.hasNext()) {
                            QGraphicsItem *item = it.next();
                            if (item->boundingRegion(item->sceneTransform())
                                    .contains(evt->pos().toPoint())) {
                              item->setSelected(!item->isSelected());
                              break;  // This can happen twice cuz of float rounding nonsense
                            }
                          }
                          break;
                        }
                        case Qt::RightButton: {
                          clearSelection();
                          break;
                        }
                        default: {
                          break;
                        }
                      }
                      QGraphicsScene::mousePressEvent(evt);
                    }
                    

                    The reason I don't just use the graphics view mouse event is because, even though it was working before to an extent, it broke when I scrolled because the mouse event's position doesn't scroll with it.
                    This doesn't work the same as that did, though, when it comes to selection of the whole item. Now I'm back where I started and am considering creating a rectangle around the point & getting the containing items of that & doing the boundingRect() logic myself. The code that somewhat worked before:

                    void CustomGraphicsView::mousePressEvent(QMouseEvent *evt) {
                      switch (evt->button()) {
                        case Qt::LeftButton: {
                          QList<QGraphicsItem *> items =
                              this->scene()->items(evt->pos(), Qt::IntersectsItemBoundingRect);
                          QListIterator<QGraphicsItem *> it(items);
                          while (it.hasNext()) {
                            QGraphicsItem *item = it.next();
                            if (item->boundingRegion(item->sceneTransform()).contains(evt->pos())) {
                              item->setSelected(!item->isSelected());
                              break;  // This can happen twice cuz of float rounding nonsense
                            }
                          }
                          break;
                        }
                        case Qt::RightButton: {
                          scene()->clearSelection();
                          break;
                        }
                        default: {
                          break;
                        }
                      }
                      QGraphicsView::mousePressEvent(evt);
                    }
                    

                    Ewan Green

                    1 Reply Last reply
                    0

                    • Login

                    • Login or register to search.
                    • First post
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • Users
                    • Groups
                    • Search
                    • Get Qt Extensions
                    • Unsolved