3

I have a derived class of QGraphicsView where I set the drag mode to ScrollHandDrag and also implement the zoom functionality:

Header

#ifndef CUSTOMGRAPHICSVIEW_H
#define CUSTOMGRAPHICSVIEW_H

#include <QGraphicsView>

class CustomGraphicsView : public QGraphicsView
{
  Q_OBJECT
public:
  CustomGraphicsView(QWidget* parent = nullptr);

protected:
  virtual void wheelEvent(QWheelEvent* event) override;
};

#endif  // CUSTOMGRAPHICSVIEW_H

Implementation

#include "customview.h"

#include <QWheelEvent>

CustomGraphicsView::CustomGraphicsView(QWidget* parent) : QGraphicsView(parent)
{
  setScene(new QGraphicsScene);
  setDragMode(ScrollHandDrag);
}

void CustomGraphicsView::wheelEvent(QWheelEvent* event)
{
  // if ctrl pressed, use original functionality
  if (event->modifiers() & Qt::ControlModifier)
    QGraphicsView::wheelEvent(event);
  // otherwise, do yours
  else
  {
    setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    if (event->delta() > 0)
    {
      scale(1.1, 1.1);
    }
    else
    {
      scale(0.9, 0.9);
    }
  }
}

When I use this class in a program (see below), I can move around the scene and zoom in and out. However, when the image is bigger in one of the dimension than the viewport, but not in the other one (see attached image) I can only drag along the axis that coincides with the image being bigger than the. This, in the attached image, is vertical as it can be seen by the presence of the right-hand side scroll bar.

My question is: is there a way to not restrict the movement? Can I set the scroll mode that allows me to move freely regardless of the scene being contained in the view? Is my only option to reimplement mouseMoveEvent?

enter image description here

Application

#include <QApplication>
#include <QGraphicsPixmapItem>
#include "customview.h"

int main(int argc, char** argv)
{
  QApplication app(argc, argv);
  CustomGraphicsView cgv;
  QGraphicsPixmapItem* item = new QGraphicsPixmapItem(QPixmap::fromImage(QImage("clouds-country-daylight-371633.jpg")));
  cgv.scene()->addItem(item);
  cgv.show();
  return app.exec();
}

The image I used is this one.

apalomer
  • 1,895
  • 14
  • 36

1 Answers1

3

After a careful read of the documentation, my conclusion is that it is not possible to move outside the scene. However, one can manually set the limits of the scene to something bigger than the actual scene. The easiest solution is to set a big enough scene at the beginning as suggested here. However, this is not dynamic and has limitations. I solved this issue by auto-computing the scene limits whenever the scene is updated. For that, I connect the QGraphicsScene::changed to a slot where the auto size of the scene is computed and I manually force the scene to be updated with the mouse move. The final class with the desired behavior is:

Header

#ifndef CUSTOMGRAPHICSVIEW_H
#define CUSTOMGRAPHICSVIEW_H

#include <QGraphicsView>

class CustomGraphicsView : public QGraphicsView
{
  Q_OBJECT
public:
  CustomGraphicsView(QWidget* parent = nullptr);

protected:
  virtual void wheelEvent(QWheelEvent* event) override;
  virtual void mouseMoveEvent(QMouseEvent* event) override;
  virtual void mousePressEvent(QMouseEvent* event) override;
  virtual void mouseReleaseEvent(QMouseEvent* event) override;

  void autocomputeSceneSize(const QList<QRectF>& region);
};

#endif  // CUSTOMGRAPHICSVIEW_H

CPP

#include "customview.h"

#include <QWheelEvent>

CustomGraphicsView::CustomGraphicsView(QWidget* parent) : QGraphicsView(parent)
{
  // Set up new scene
  setScene(new QGraphicsScene);

  // Do not show scroll bars
  setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

  // Connect scene update to autoresize
  connect(scene(), &QGraphicsScene::changed, this, &CustomGraphicsView::autocomputeSceneSize);
}

void CustomGraphicsView::wheelEvent(QWheelEvent* event)
{
  // if ctrl pressed, use original functionality
  if (event->modifiers() & Qt::ControlModifier)
    QGraphicsView::wheelEvent(event);
  // Rotate scene
  else if (event->modifiers() & Qt::ShiftModifier)
  {
    if (event->delta() > 0)
    {
      rotate(1);
    }
    else
    {
      rotate(-1);
    }
  }
  // Zoom
  else
  {
    ViewportAnchor previous_anchor = transformationAnchor();
    setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    if (event->delta() > 0)
    {
      scale(1.1, 1.1);
    }
    else
    {
      scale(0.9, 0.9);
    }
    setTransformationAnchor(previous_anchor);
  }
}

void CustomGraphicsView::mouseMoveEvent(QMouseEvent* event)
{
  QGraphicsView::mouseMoveEvent(event);
  if (event->buttons() & Qt::LeftButton)
    // If we are moveing with the left button down, update the scene to trigger autocompute
    scene()->update(mapToScene(rect()).boundingRect());
}

void CustomGraphicsView::mousePressEvent(QMouseEvent* event)
{
  if (event->buttons() & Qt::LeftButton)
    // Set drag mode when left button is pressed
    setDragMode(QGraphicsView::ScrollHandDrag);
  QGraphicsView::mousePressEvent(event);
}

void CustomGraphicsView::mouseReleaseEvent(QMouseEvent* event)
{
  if (dragMode() & QGraphicsView::ScrollHandDrag)
    // Unset drag mode when left button is released
    setDragMode(QGraphicsView::NoDrag);
  QGraphicsView::mouseReleaseEvent(event);
}

void CustomGraphicsView::autocomputeSceneSize(const QList<QRectF>& region)
{
  Q_UNUSED(region);

  // Widget viewport recangle
  QRectF widget_rect_in_scene(mapToScene(-20, -20), mapToScene(rect().bottomRight() + QPoint(20, 20)));

  // Copy the new size from the old one
  QPointF new_top_left(sceneRect().topLeft());
  QPointF new_bottom_right(sceneRect().bottomRight());

  // Check that the scene has a bigger limit in the top side
  if (sceneRect().top() > widget_rect_in_scene.top())
    new_top_left.setY(widget_rect_in_scene.top());

  // Check that the scene has a bigger limit in the bottom side
  if (sceneRect().bottom() < widget_rect_in_scene.bottom())
    new_bottom_right.setY(widget_rect_in_scene.bottom());

  // Check that the scene has a bigger limit in the left side
  if (sceneRect().left() > widget_rect_in_scene.left())
    new_top_left.setX(widget_rect_in_scene.left());

  // Check that the scene has a bigger limit in the right side
  if (sceneRect().right() < widget_rect_in_scene.right())
    new_bottom_right.setX(widget_rect_in_scene.right());

  // Set new scene size
  setSceneRect(QRectF(new_top_left, new_bottom_right));
}
apalomer
  • 1,895
  • 14
  • 36
  • That's a pretty smart solution, but mouse move works only when the view is not zoomed or only zoomed IN (you can notice what I mean by enabling the scrollbars e trying to drag the view) – JuanDeLosMuertos Nov 05 '20 at 14:27