in Code

Extending Selections In QGraphicsView

Overall, the Qt library has done a fantastic job of maintaining relatively simple interfaces with well-named functions that do what they say they’re going to do [unlike MFC for example] and of having objects react the way you would expect them to without much modification. I’ve used it for several cross-platform Windows/Mac OS X/Linux applications and I can usually bend it to my will fairly easily because they’ve provided the right hooks.

One exception I’ve run into recently – and an oversight that is really kind of surprising – is using rubber band selections on a QGraphicsView. What would be natural – and what most people would expect – is that if the user is holding down the “modify selection” key – Control on Windows, Command on Mac OS X – then:

  • clicking a selected object will deselect it without modifying the rest of the selection
  • clicking an unselected object will select it without modifying the rest of the selection
  • clicking and dragging will give the user a way to select several objects to add to the current selection (known as rubber band or drag selection).

QGraphicsView With Selection

QGraphicsView With Objects Selected In Red

But with QGraphicsView – by design – if you have some objects selected (as pictured above), there’s no way to extend the selection by using “rubber band selection” to add to it. Clicking the mouse clears the current selection regardless of whether the user is holding the “modify selection” key. This is a UX design bug. It is counter-intuitive and confusing for users.

QGraphicsView Selection Method

QGraphicsView Selection Method - Holding Down The Control/Command Key Resets Selection

Not only is the default behaviour confusing, there’s no way to correct it without either inheriting from QGraphicsView and writing a new rubber band selection system or hacking the Qt source. I chose to fix the problem by doing the latter. Fortunately this turned out to be easier than I originally thought it would be…

(19 Jan 2015): I submitted a patch for this which has been merged into the Qt codebase. This fix will be part of Qt 5.5.

The general solution is as follows [my changes marked with // [ASM] Multi-selection]:

  1. Add a QList<QGraphicsItems *> member to QGraphicsViewPrivate in qgraphicsview_p.h to keep track of any current selection.
  2. In QGraphicsView::mousePressEvent() in qgraphicsview.cpp, if we’ve determined that the user is “rubber banding”, check the modifier keys to see if the Control/Command key is down. If it is, save the current selection to our new private QList<QGraphicsItems *> member on QGraphicsViewPrivate. If the modifier key isn’t down, then clear the selection as we used to.
  3. Add a parameter to QGraphicsScene::setSelectionArea() in qgraphicsscene.h to pass in a pointer to our current selection.
  4. As the user drags the selection box, call QGraphicsScene::setSelectionArea() with the new parameter in QGraphicsView::mouseMoveEvent() in qgraphicsview.cpp.
  5. In the modified QGraphicsScene::setSelectionArea(), check if we passed in a selection list and, if we did, ensure that the items remain selected.
  6. When the mouse is released, clear our new selection list in QGraphicsView::mouseReleaseEvent().
  7. There is one additional case where we need to check keyboard modifiers – QGraphicsScenePrivate::mousePressEventHandler(). Previously, this would clear the selection if our drag mode was not QGraphicsView::ScrollHandDrag, so we add a check for our modifiers and clear the selection if it is not down.

That’s it. Now the rubber band selection works as expected when the “modify selection” key is down.

QGraphicsView Extending Selection Using Rubber Band

Extending Selection Using Rubber Band and Holding the Control/Command Key

If you find this useful, or find any problems with it, please let me know!

Patch file: QGraphicsView_Multi_Select.patch

(19 Jan 2015): I submitted a patch for this which has been merged into the Qt codebase. This fix will be part of Qt 5.5.

Notes:

  1. QGraphicsItem checks the “extend selection” key properly, allowing the user to select/deselect an item by clicking it, so no modifications were necessary there.
  2. When I modified the QGraphicsScene::setSelectionArea() function to add the current selection list parameter, I used a pointer to the QList<QGraphicsItem*>. This was so I could default it to NULL which means the change would not affect any other code currently using this function. The other option would be to add a new override and factor the code, but that seemed overkill for this.
  3. I applied this to Qt 4.8.x from git e678ed1. I haven’t looked at other versions, but it might apply to others, or at least give you a head start if you want to implement something like this on other versions of Qt.
  4. This bug fix has only been tested with my code for my specific case. I can’t see how what I did would affect anything else, but no guarantees!

Write a Comment

Comment

  1. Qt5 has the same issue. I’m a bit surprised that no one besides you (and now me) talk about this issue. This makes the selection in QGraphicsView unusable in such a way that one needs to either patch Qt like you did, or one has to rewrite the entire selection handling again.

    If I find the time to apply your patch to Qt5, and if it works, we should even consider submitting this patch to Qt5 in a binary, source and behavior compatible way…

    • This makes the selection in QGraphicsView unusable…

      I absolutely agree. It’s a surprising oversight.

      I have submitted patches in the past, but it’s a complicated process relative to other projects I contribute to. Since I don’t do it frequently, I need to relearn it every single time, so I probably don’t contribute as often as I would otherwise.

      I am in the process of moving my work to Qt 5, so I’m sure I’ll run into this again eventually and want a more permanent fix…

    • Do you mean safe in the ABI sense or safe in the functionality sense?

      On the ABI side, I had to add a parameter to QGraphicsScene::setSelectionArea(), so I think this causes ABI incompatibility you would have to work around (duplicate the function) if that’s an issue.

      On the functionality side, I have only used it on Qt 4, but yes I believe it’s safe. As I mentioned, I actually see this as a bug fix and I tried to keep the changes as minimal as possible.

      I haven’t tried it on Qt 5 yet and haven’t looked at Qt 5 to see what they might have changed.

      My software ships with a modified Qt 4 and I haven’t had any issues with this fix on Mac or Windows. I don’t see anything that would prevent it working on Linux or any other platform either.

      If you do try it, I’d appreciate any feedback you might have.

      Thanks for stopping by!