2
votes

I have a window, in which is a QGraphicsView, which will be displaying an image. I have implemented the wheelEvent(). My images are mostly going to be bigger than the window, so I get scroll bars in the window.

What we normally observe when we rotate the wheel while viewing an image in Windows Photo Viewer is that when we move the wheel up (towards it's wire), the image zooms in and when we move it down (towards out body), the image zooms out.

What I am getting instead is when I move the wheel towards myself (to zoom out) the image instead of zooming out , first scrolls down, and starts zooming out only when the scroll bar touches its bottom most point.

It would be better to understand the problem by trying out the code. I am not able to explain in I guess.

I want the standard behavior. What to do?

Code

#include "viewer.h"
#include "ui_viewer.h"
#include <QGraphicsView>
#include <QGraphicsItem>
#include <QGraphicsPixmapItem>
#include <QWheelEvent>
#include <QDebug>
#include <QImage>
#include <QImageReader>
#include <QApplication>
#include <QDesktopWidget>

viewer::viewer(QWidget *parent) : QWidget(parent),ui2(new Ui::viewer)
{
     ui2->setupUi(this);
} 

viewer::~viewer()
{
    delete ui2;
}

int viewer::show_changes(QString folder)
{
    QDesktopWidget *desktop = QApplication::desktop();
    int screenWidth  = desktop->width();
    int screenHeight = desktop->height();
    QString filename = "image_bigger_than_window.jpg";
    QPixmap pixmap = QPixmap(filename);
    QImageReader reader(filename);
    QImage image = reader.read();
    QSize size = image.size();
    int width = 800;
    int height = (width * size.height()) / size.width();
    int x = (screenWidth - width) / 2;
    int y = (screenHeight - height) / 2 - 30;
    setGeometry(x,y,width, height);
    setWindowTitle("OUTPUT");
    ui2->graphicsView->setGeometry(0,0,width,height);
    QGraphicsScene* viewScene = new QGraphicsScene(QRectF(0, 0,width,  height), 0);
    QGraphicsPixmapItem *item = viewScene->addPixmap(pixmap.scaled(QSize((int)viewScene->width(), (int)viewScene->height()),
                             Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
    viewScene->addItem(item);
    ui2->graphicsView->fitInView(QRectF(0, 0, width,     height),Qt::IgnoreAspectRatio);
    ui2->graphicsView->setScene(viewScene);
    ui2->graphicsView->show();
    return 0;
}

void viewer::wheelEvent(QWheelEvent * event)
{
    const int degrees = event->delta() / 8;
    qDebug() << degrees;
    int steps = degrees / 15;        
    double scaleFactor = 1.0; 
    const qreal minFactor = 1.0;
    const qreal maxFactor = 10.0;
    qreal h11 = 1.0, h22 = 0;
    if(steps > 0)
    {
        h11 = (h11 >= maxFactor) ? h11 : (h11 + scaleFactor);
        h22 = (h22 >= maxFactor) ? h22 : (h22 + scaleFactor);
    }
    else
    {
        h11 = (h11 <= minFactor) ? minFactor : (h11 - scaleFactor);
        h22 = (h22 <= minFactor) ? minFactor : (h22 - scaleFactor);
    }
    ui2->graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    ui2->graphicsView->setTransform(QTransform(h11, 0, 0,0, h22, 0, 0,0,1));

}

EDIT

1)Removed function void viewer::wheelEvent(QWheelEvent * event) from viewer.cpp

2)Put bool viewer::eventFilter(QObject *obj, QEvent *event) in its place as a protected function and void viewer::handleWheelOnGraphicsScene(QGraphicsSceneWheelEvent* scrollevent) as a public slot in viewer.h

bool viewer::eventFilter(QObject *obj, QEvent *event)
{
    if (event->type() == QEvent::GraphicsSceneWheel)
    {    
        QGraphicsSceneWheelEvent *scrollevent = static_cast<QGraphicsSceneWheelEvent *>(event);
        handleWheelOnGraphicsScene(scrollevent);
        return true;
    }
  // Other events should propagate - what do you mean by propagate here?
  return false;
}

 void viewer::handleWheelOnGraphicsScene(QGraphicsSceneWheelEvent* scrollevent)
{
   const int degrees = scrollevent->delta()  / 8;
   qDebug() << degrees;
   int steps = degrees / 15;
   qDebug() << steps;
   double scaleFactor = 1.0; //How fast we zoom
   const qreal minFactor = 1.0;
   const qreal maxFactor = 10.0;
   if(steps > 0)
   {
      h11 = (h11 >= maxFactor) ? h11 : (h11 + scaleFactor);
      h22 = (h22 >= maxFactor) ? h22 : (h22 + scaleFactor);
   }
   else
  {
      h11 = (h11 <= minFactor) ? minFactor : (h11 - scaleFactor);
      h22 = (h22 <= minFactor) ? minFactor : (h22 - scaleFactor);
  } 
ui2->graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
ui2->graphicsView->setTransform(QTransform(h11, 0, 0,0, h22, 0, 0,0,1));

}
1
With propagate I mean that events are handled in a chain. When an event is sent to a widget that does not handle it (e.g. Wheel events on Push Buttons), it will be propagated to its parent widget so it can handle it (e.g. a Scroll View that contains the button). The same is for event filters.king_nak

1 Answers

2
votes

The code shows that you didn't subclass QGraphicsView, but instead use one in your own widget.

The wheel event will be first sent to the actual graphics view widget. There it is handled with Qt's default behaviour, namely scrolling. Only if you scrolled to the bottom, the graphics view cannot handle the wheel event, and it is propagated to its parent, your class. That's why you only can zoom when scrolled to the border.

To fix this, you should install an event filter. That allows you to intercept the wheel event and process it in your class:

// Outline, not tested
viewer::viewer(QWidget *parent) : QWidget(parent),ui2(new Ui::viewer)
{
    ui2->setupUi(this);
    // Let me handle your events
    ui2->graphicsView->installEventFilter(this);
} 

// should be protected
bool viewer::eventFilter(QObject *obj, QEvent *event) {
  if (event->type() == QEvent::GraphicsSceneWheel) {
      // Your implementation. 
      // You can't use QWheelEvent, as Graphicscene works with its own events...
     handleWheelOnGraphicsScene(static_cast<QGraphicsSceneWheelEvent*> (event));

     // Don't propagate
     return true;
  }

  // Other events should propagate
  return false;
}

Update
I just figured out that the event filter will not recieve GraphicsSceneWheel events on the graphics view. Instead, you have to install the filter on the Graphics Scene. Also, you have to call event->accept() so that it will not propagated.

So Updated Code:

// In Constructor, or where appropriate
ui2->graphicsView->scene()->installEventFilter(this);


bool viewer::eventFilter(QObject *obj, QEvent *event) {
    if (event->type() == QEvent::GraphicsSceneWheel) {
        handleWheelOnGraphicsScene(static_cast<QGraphicsSceneWheelEvent*> (event));

        // Don't propagate
        event->accept();
        return true;
    }
    return false;
}

also note that handleWheelOnGraphicsScene or whatever you want to call it, should be a private method, and doesn't have to be a slot.