Results 1 to 13 of 13

Thread: Implementing scale-invariant QGraphicsItems

  1. #1
    Join Date
    Oct 2006
    Posts
    279
    Thanks
    6
    Thanked 40 Times in 39 Posts
    Qt products
    Qt4
    Platforms
    MacOS X Unix/X11 Windows

    Default Implementing scale-invariant QGraphicsItems

    I want to dispay objects in a QGraphicsView and let the user resize the objects using "grab handles". The handles should be squares of a fixed size i.e. 3x3 pixels.

    I know how to draw the handles scale- and rotation-invariant, but how do i perform the hit test? shape() returns scene coordinates and I don't know where else to implement this functionality.

    This seems like a very general problem which I'd think many users would stumble upon, but so far I have no concept of how to solve it.

    Thanks

  2. #2
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: Implementing scale-invariant QGraphicsItems

    I guess you should reimplement QGraphicsItem::mousePressEvent() and QGraphicsItem::mouseMoveEvent() for your items and implement the functionality there.

  3. #3
    Join Date
    Oct 2006
    Posts
    279
    Thanks
    6
    Thanked 40 Times in 39 Posts
    Qt products
    Qt4
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Implementing scale-invariant QGraphicsItems

    Thanks for the answer! I am sorry if my last post wasn't very clear.

    The problem is by the time I receive a mouse event, the hit test has already been performed.

    To clarify:
    I want to receive a mousePressEvent if the user clicks within a 3x3 pixel area. Not considering the current rotation or scaling.

    I'd appreciate any suggestions!

  4. #4
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: Implementing scale-invariant QGraphicsItems

    I think you can do it in two ways:
    1. compose your object from a rectangle "parent" object and "child" proper object and only rotate the child, so that the parent always keeps it alignment. You'll probably avoid the need to reimplement the shape() method this way as the rectangle will provide a correct shape automatically
    2. reimplement shape() for your object and make sure it always returns an aligned rectangle including the "handle" of your object which you want to use for dragging. You can intersect or sum two shapes (the outer rectangle and the inner proper shape) to have a shape consisting of the proper shape and the handle leaving the remaining space empty. I hope I'm explaining this clear enough... I have to get some sleep, maybe people will understand me better then

  5. The following user says thank you to wysota for this useful post:

    spud (26th October 2006)

  6. #5
    Join Date
    Oct 2006
    Posts
    279
    Thanks
    6
    Thanked 40 Times in 39 Posts
    Qt products
    Qt4
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Implementing scale-invariant QGraphicsItems

    Now we're getting somewhere.
    The problem with those two solutions is that they are scale sensitive. I've come up with a solution overriding QGraphicView's drawForeground() and mouseEvents.

    It's not perfect but it's a start.

    As I said, I think this should be a common problem. Quite often you want to render a scene where most objects should be completely scalable, whereas others(text, lines, points) should be rendered with a fixed pixel size.

    Qt Code:
    1. #include <QApplication>
    2. #include <QGraphicsScene>
    3. #include <QGraphicsView>
    4. #include <QGraphicsEllipseItem>
    5. #include <QWheelEvent>
    6.  
    7. static const QRect selectRect(-1,-1,3,3);
    8.  
    9. class GraphicsView : public QGraphicsView
    10. {
    11. public:
    12. GraphicsView(QGraphicsScene *scene, QWidget *parent = 0)
    13. : QGraphicsView(scene, parent)
    14. , grabItem(0)
    15. {}
    16. void drawForeground(QPainter* painter, const QRectF& rect)
    17. {
    18. painter->setRenderHint(QPainter::Antialiasing, false);
    19. foreach(QGraphicsItem*item, scene()->items()){
    20. QGraphicsEllipseItem* ellipse = qgraphicsitem_cast<QGraphicsEllipseItem*>(item);
    21. if(ellipse && ellipse->isSelected())
    22. {
    23. QPoint point1 = mapFromScene(ellipse->scenePos()+ellipse->rect().topLeft() );
    24. QPoint point2 = mapFromScene(ellipse->scenePos()+ellipse->rect().bottomRight() );
    25. painter->setMatrixEnabled(false);
    26. painter->setBrush(Qt::black);
    27. painter->drawRect(selectRect.translated(point1));
    28. painter->drawRect(selectRect.translated(point2));
    29. }
    30. }
    31. }
    32. QGraphicsEllipseItem* hittest(QPoint point, bool& topleft)
    33. {
    34. foreach(QGraphicsItem*item, scene()->items()){
    35. QGraphicsEllipseItem* ellipse = qgraphicsitem_cast<QGraphicsEllipseItem*>(item);
    36. if(ellipse && ellipse->isSelected())
    37. {
    38. QPoint diff = mapFromScene(ellipse->scenePos()+ellipse->rect().topLeft())-point;
    39. if(selectRect.adjusted(-1,-1,1,1).contains(diff)){
    40. topleft=true;
    41. return ellipse;
    42. }
    43.  
    44. diff = mapFromScene(ellipse->scenePos()+ellipse->rect().bottomRight())-point;
    45. if(selectRect.adjusted(-1,-1,1,1).contains(diff)){
    46. topleft=false;
    47. return ellipse;
    48. }
    49. }
    50. }
    51. return 0;
    52. }
    53. void mousePressEvent(QMouseEvent*event)
    54. {
    55. mousePos = event->pos();
    56. grabItem = hittest(mousePos, topleft);
    57. if(grabItem==0)
    58. QGraphicsView::mousePressEvent(event);
    59. }
    60. void mouseMoveEvent(QMouseEvent*event)
    61. {
    62. if(grabItem){
    63. QPoint newPos = event->pos();
    64. QPointF diff = mapToScene(newPos)-mapToScene(mousePos);
    65. if(topleft)
    66. grabItem->setRect(grabItem->rect().adjusted(diff.x(), diff.y(),0,0));
    67. else
    68. grabItem->setRect(grabItem->rect().adjusted(0,0,diff.x(), diff.y()));
    69. mousePos=newPos;
    70. }
    71. else
    72. QGraphicsView::mouseMoveEvent(event);
    73. }
    74. void mouseReleaseEvent(QMouseEvent*event)
    75. {
    76. if(grabItem)
    77. grabItem=0;
    78. else
    79. QGraphicsView::mouseReleaseEvent(event);
    80. }
    81. void wheelEvent(QWheelEvent* event)
    82. {
    83. if(event->delta()<0)
    84. scale(0.8, 0.8);
    85. else
    86. scale(1.25, 1.25);
    87. }
    88. private:
    89. QPoint mousePos;
    90. bool topleft;
    91. };
    92.  
    93. int main(int argc, char *argv[])
    94. {
    95. QApplication app(argc, argv);
    96.  
    97. GraphicsView* view = new GraphicsView(&scene);
    98. view->setRenderHint(QPainter::Antialiasing);
    99.  
    100. ellipse = new QGraphicsEllipseItem(QRect(0,0,50,50),0, &scene);
    101. ellipse->setFlags(QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemIsSelectable);
    102.  
    103. ellipse = new QGraphicsEllipseItem(QRect(0,0,50,50),0, &scene);
    104. ellipse->setFlags(QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemIsSelectable);
    105.  
    106. ellipse->setPos(100,50);
    107.  
    108. view->show();
    109.  
    110. return app.exec();
    111. }
    To copy to clipboard, switch view to plain text mode 

  7. #6
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: Implementing scale-invariant QGraphicsItems

    If you only make the child object scalable, the result should be scale insensitive.

  8. #7
    Join Date
    Oct 2006
    Posts
    279
    Thanks
    6
    Thanked 40 Times in 39 Posts
    Qt products
    Qt4
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Implementing scale-invariant QGraphicsItems

    It would be scale insensitive to object and scene transformations perhaps. But not to the transformation of the view(s).

  9. #8
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: Implementing scale-invariant QGraphicsItems

    Quote Originally Posted by spud View Post
    It would be scale insensitive to object and scene transformations perhaps. But not to the transformation of the view(s).
    Yes, this is true. But it will be if you don't paint the handles as an object but paint directly on the canvas instead. drawForeground() is something to start with, as you already mentioned.

    You could also try something else. It should be possible to retrieve the transformation matrix for the view (QGraphicsView::matrix()). Then, if you inverted the matrix and applied it to the painter, you could paint scale-insensitive things in every item you want - you'd just "revert" the scaling for a little time.

  10. #9
    Join Date
    Jan 2006
    Location
    Norway
    Posts
    124
    Thanked 38 Times in 30 Posts
    Qt products
    Qt3 Qt4 Qt/Embedded
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Implementing scale-invariant QGraphicsItems

    In general, though, scale-invariant items break the update contracts between the scene and the view, so if you try to implement them using a reverse-mapped transformation matrix, you're likely to see rendering bugs.

    The view and scene really need to cooperate to make something like this work. We're researching it, and maybe we can come up with a good solution.
    Bitto / Andreas Aardal Hanssen - andreas dot aardal dot hanssen at nokia
    Nokia Software Manager, Qt Development

  11. #10
    Join Date
    Oct 2007
    Posts
    1
    Qt products
    Qt4
    Platforms
    Unix/X11

    Default Re: Implementing scale-invariant QGraphicsItems

    I had the same problem: multiple QGraphicsViews on the same QGraphicScene containing pointlike objects. Those objects should be represented as circles with a fixed radius (e.g. 4 pixels) and attached with a Label (short Text).

    Painting them is not a problem: Having a QPainter, you can always use QPainter::resetTransform() to get back to the Widget coordinates an draw the circle and the label "upright" and untransformed on the screen.

    And here is the trick I use to grab the mouse: I define a boundingRect for the pointlike items which is so large that it includes the Text for every reasonable scale ans orientation used in the views. This is necessary for correct painting anyway.

    As a result, the item receives every mouse event which hits into the large boundingRect. To check whether the event actually hit the small circle, one has to check the distance between the position of the item and the position of the mouse click in viewport coordinates of the view in which the mouse was pressed. The view is available from the QGraphicsSceneMouseEvent by qobject_cast<QGraphicsView*>( event->widget()->parent() ).

    Using the various map-functions, both the item coordinates and the mouse click coordinates can be transformed to view coordinates, and then the distance in pixes can be computed. If the mouse click was outside the circle, simply ignore() the event and it will be passed to the next candidate.

    Qt Code:
    1. void PointItem::mousePressEvent( QGraphicsSceneMouseEvent * ev ) {
    2. QGraphicsView * view = qobject_cast<QGraphicsView*>( ev->widget()->parent() );
    3. /* check here if view is valid -- */
    4. QPointF item_pos = view->mapFromScene( scenePos() );
    5. QPointF mouse_pos = view->mapFromScene( ev->scenePos() );
    6. bool ok = /* check if mouse_pos lies within 4 pixes around item_pos */
    7. if ( ok ) {
    8. /* grab the mouse and do something */
    9. } else {
    10. ev->ignore();
    11. }
    12. };
    To copy to clipboard, switch view to plain text mode 

  12. #11
    Join Date
    Jan 2006
    Location
    Norway
    Posts
    124
    Thanked 38 Times in 30 Posts
    Qt products
    Qt3 Qt4 Qt/Embedded
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Implementing scale-invariant QGraphicsItems

    Have you looked at QGraphicsItem::ItemIgnoresTransformations?
    Bitto / Andreas Aardal Hanssen - andreas dot aardal dot hanssen at nokia
    Nokia Software Manager, Qt Development

  13. #12
    Join Date
    May 2007
    Posts
    19
    Thanks
    5
    Thanked 1 Time in 1 Post
    Qt products
    Qt4
    Platforms
    Unix/X11 Windows

    Default Re: Implementing scale-invariant QGraphicsItems

    Quote Originally Posted by spud View Post
    I know how to draw the handles scale- and rotation-invariant
    How do you do that? I am looking for a solution in this thread, but have not yet found a satisfactory result.

  14. #13
    Join Date
    Jan 2012
    Posts
    1
    Qt products
    Qt4
    Platforms
    Unix/X11 Windows

    Default Re: Implementing scale-invariant QGraphicsItems

    class SimpleItem : public QGraphicsItem
    {
    public:
    ...
    SimpleItem (int dx, int dy){
    setFlags(ItemIsSelectable | ItemIsMovable | ItemIgnoresTransformations);
    setAcceptsHoverEvents(true);
    nodeRectangle.setRect(0,0,100,30);

    ...
    }

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.