Using Qt's QTimer function to make animations in OpenGl - c++

How exactly do you use a QTimer to set off an animation in OpenGl?
I want to draw a simple circle and change the radius every 30 milliseconds, so it appears to grow and shrink smoothly.
Here's what I've come up with so far:
Header File
#include <QGLWidget>
#include <QTimer>
class GLWidget : public QGLWidget
{
Q_OBJECT
public:
explicit GLWidget(QWidget *parent = 0);
protected:
void initializeGL();
void paintGL();
void resizeGL(int width, int height);
void timerEvent(QTimerEvent *event);
private:
QBasicTimer timer;
private slots:
void animate();
};
CPP File
int circRad = 0;
GLWidget::GLWidget(QWidget *parent) :
QGLWidget(parent)
{
QTimer *aTimer = new QTimer;
connect(aTimer,SIGNAL(timeout(QPrivateSignal)),SLOT(animate()));
aTimer->start(30);
}
void GLWidget::initializeGL()
{
glClearColor(1,1,1,0);
}
void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(0,0,1);
const float DEG2RAD = 3.14159/180;
glBegin(GL_LINE_LOOP);
for (int i=0; i <= 360; i++)
{
float degInRad = i*DEG2RAD;
glVertex2f(cos(degInRad)*circRad,sin(degInRad)*circRad);
}
glEnd();
}
void GLWidget::resizeGL(int width, int height)
{
glViewport(0,0,width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void GLWidget::animate()
{
if(circRad < 6)
{
circRad = circRad + 1;
}
else
{
circRad = circRad - 1;
}
update();
}
This(suprise, suprise) does nothing. Am I supposed to call a QTimerEvent? If so, does that mean I remove the animate SLOT and replace it with the QTimerEvent? Do I put the code from animate() into the QTimerEvent?

Typically you would only use a timer to trigger repaints, e.g. to limit the frame rate to 60 FPS. In the paint method, you would then check the current time, and do what you need to do to animate stuff. E.g. store the time t_start when the circle started growing, then offset the radius by sin(t - t_start).
By using the time (instead of the number of frames) you get animation that is independent of the frame rate. Keep in mind that Qt's timers are not exact. If you set a repeat interval of 30 ms, Qt doesn't guarantee that the slot is going to get called every 30 ms. Sometimes it might be 30 ms, sometimes 40 or even 100, depending on what else is in the event queue, or what's blocking the UI thread. If these hiccups occur, you don't want your animation to slow down.
Oh, and don't use int for the circle radius. If you want smooth animation, always use float or double.

QPrivateSignal should not be part of the signal signature in the connect call:
connect(aTimer,SIGNAL(timeout()),SLOT(animate()));
QtCreator's completion doesn't ignore it yet as it should (there is a bug report about that).

Related

Draw ellipse delay and draw another one

i need to draw a ellipse and after some delay draw another one.
I having trouble doing this.
This is a simplified version of the code that i'm actually doing, but i belive this will help me solve the problem
here is the code.
MyView::MyView()
{
sc = new QGraphicsScene();
this->setSceneRect(0,0,800,600);
this->setFixedSize(800,600);
this->setStyleSheet("QScrollBar {height:0px;}");
this->setStyleSheet("QScrollBar {width:0px;}");
sc->setSceneRect(0,0,800,600);
this->setScene(sc);
}
void MyView::mousePressEvent(QMouseEvent *event)
{
sc->addEllipse(event->x(),event->y(),10,10,QPen(),QBrush(Qt::red));
int i=0;
while(i < 1000000000) // SIMULATING DELAY
i++; //
sc->addEllipse(event->y(),event->x(),10,10,QPen(),QBrush(Qt::blue));
}
class MyView : public QGraphicsView
{
public:
MyView();
QGraphicsScene *sc;
public slots:
void mousePressEvent(QMouseEvent *event);
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyView wv;
wv.show();
return a.exec();
}
the first ellipse is not showing up until the while over and the second one appears
it's doesn't matter how long is the while. Always draw the two ellipses at the same time.
In the main thread of the GUI you should not have tasks that take too much time since they block the eventloop preventing the GUI's own work from being performed. In your case, that while loop can be replaced by a QTimer:
void MyView::mousePressEvent(QMouseEvent *event)
{
QPointF p = mapToScene(event->pos());
sc->addEllipse(QRectF(p, QSizeF(10, 10)), QPen(), QBrush(Qt::red));
// 1000 is the delay in ms
QTimer::singleShot(1000, this, [this, p](){
sc->addEllipse(QRectF(p, QSizeF(10, 10)), QPen(), QBrush(Qt::blue));
});
}

Translate screen drawing to another part of the screen

Is it possible to make it so that all drawing to an area "A" is translated to an area "B"?
For example drawing to the area(0,0)(100,100) and have it appear in area(200,200)(300,300).
The question is actually tagged with windows and graphics. This might have been targeted to Win32 and GDI (where I've unfortunately nearly no experience). So, the following might be seen as proof of concept:
I couldn't resist to implement the idea / concept using QWindow and QPixmap.
The concept is:
open a window fullscreen (i.e. without decoration)
make a snapshot and store it internally (in my case a )
display the internal image in window (the user cannot notice the difference)
perform a loop where pixmap is modified and re-displayed periodically (depending or not depending on user input).
And this is how I did it in Qt:
I opened a QWindow and made it fullscreen. (Maximum size may make the window full screen as well but it still will have decoration (titlebar with system menu etc.) which is unintended.)
Before painting anything, a snapshot of this window is done. That's really easy in Qt using QScreen::grabWindow(). The grabbed contents is returned as QPixmap and stored as member of my derived Window class.
The visual output just paints the stored member QPixmap.
I used a QTimer to force periodical changes of the QPixmap. To keep the sample code as short as possible, I didn't make the effort of shuffling tiles. Instead, I simply scrolled the pixmap copying a small part, moving the rest upwards, and inserting the small stripe at bottom again.
The sample code qWindowRoll.cc:
#include <QtWidgets>
class Window: public QWindow {
private:
// the Qt backing store for window
QBackingStore _qBackStore;
// background pixmap
QPixmap _qPixmap;
public:
// constructor.
Window():
QWindow(),
_qBackStore(this)
{
showFullScreen();
}
// destructor.
virtual ~Window() = default;
// disabled:
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
// do something with pixmap
void changePixmap()
{
enum { n = 4 };
if (_qPixmap.height() < n) return; // not yet initialized
const QPixmap qPixmapTmp = _qPixmap.copy(0, 0, _qPixmap.width(), n);
//_qPixmap.scroll(0, -n, 0, n, _qPixmap.width(), _qPixmap.height() - n);
{ QPainter qPainter(&_qPixmap);
qPainter.drawPixmap(
QRect(0, 0, _qPixmap.width(), _qPixmap.height() - n),
_qPixmap,
QRect(0, n, _qPixmap.width(), _qPixmap.height() - n));
qPainter.drawPixmap(0, _qPixmap.height() - n, qPixmapTmp);
}
requestUpdate();
}
protected: // overloaded events
virtual bool event(QEvent *pQEvent) override
{
if (pQEvent->type() == QEvent::UpdateRequest) {
paint();
return true;
}
return QWindow::event(pQEvent);
}
virtual void resizeEvent(QResizeEvent *pQEvent)
{
_qBackStore.resize(pQEvent->size());
paint();
}
virtual void exposeEvent(QExposeEvent*) override
{
paint();
}
// shoot screen
// inspired by http://doc.qt.io/qt-5/qtwidgets-desktop-screenshot-screenshot-cpp.html
void makeScreenShot()
{
if (QScreen *pQScr = screen()) {
_qPixmap = pQScr->grabWindow(winId());
}
}
private: // internal stuff
// paint
void paint()
{
if (!isExposed()) return;
QRect qRect(0, 0, width(), height());
if (_qPixmap.width() != width() || _qPixmap.height() != height()) {
makeScreenShot();
}
_qBackStore.beginPaint(qRect);
QPaintDevice *pQPaintDevice = _qBackStore.paintDevice();
QPainter qPainter(pQPaintDevice);
qPainter.drawPixmap(0, 0, _qPixmap);
_qBackStore.endPaint();
_qBackStore.flush(qRect);
}
};
int main(int argc, char **argv)
{
QApplication app(argc, argv);
// setup GUI
Window win;
win.setVisible(true);
// setup timer
QTimer qTimer;
qTimer.setInterval(50); // 50 ms -> 20 Hz (round about)
QObject::connect(&qTimer, &QTimer::timeout,
&win, &Window::changePixmap);
qTimer.start();
// run application
return app.exec();
}
I compiled and tested with Qt 5.9.2 on Windows 10. And this is how it looks:
Note: On my desktop, the scrolling is smooth. I manually made 4 snapshots and composed a GIF in GIMP – hence the image appears a bit stuttering.

Qt video frames from camera corrupted

EDIT: The first answer solved my problem. Apart from that I had to set the ASI_BANDWIDTH_OVERLOAD value to 0.
I am programming a Linux application in C++/Qt 5.7 to track stars in my telescope. I use a camera (ZWO ASI 120MM with according SDK v0.3) and grab its frames in a while loop in a separate thread. These are then emitted to a QOpenGlWidget to be displayed. I have following problem: When the mouse is inside the QOpenGlWidget area, the displayed frames get corrupted. Especially when the mouse is moved. The problem is worst when I use an exposure time of 50ms and disappears for lower exposure times. When I feed the pipeline with alternating images from disk, the problem disappears. I assume that this is some sort of thread-synchronization problem between the camera thread and the main thread, but I couldnt solve it. The same problem appears in the openastro software. Here are parts of the code:
MainWindow:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){
mutex = new QMutex;
camThread = new QThread(this);
camera = new Camera(nullptr, mutex);
display = new GLViewer(this, mutex);
setCentralWidget(display);
cameraHandle = camera->getHandle();
connect(camThread, SIGNAL(started()), camera, SLOT(connect()));
connect(camera, SIGNAL(exposureCompleted(const QImage)), display, SLOT(showImage(const QImage)), Qt::BlockingQueuedConnection );
camera->moveToThread(camThread);
camThread->start();
}
The routine that grabs the frames:
void Camera::captureFrame(){
while( cameraIsReady && capturing ){
mutex->lock();
error = ASIGetVideoData(camID, buffer, bufferSize, int(exposure*2*1e-3)+500);
if(error == ASI_SUCCESS){
frame = QImage(buffer,width,height,QImage::Format_Indexed8).convertToFormat(QImage::Format_RGB32); //Indexed8 is for 8bit
mutex->unlock();
emit exposureCompleted(frame);
}
else {
cameraStream << "timeout" << endl;
mutex->unlock();
}
}
}
The slot that receives the image:
bool GLViewer::showImage(const QImage image)
{
mutex->lock();
mOrigImage = image;
mRenderQtImg = mOrigImage;
recalculatePosition();
updateScene();
mutex->unlock();
return true;
}
And the GL function that sets the image:
void GLViewer::renderImage()
{
makeCurrent();
glClear(GL_COLOR_BUFFER_BIT);
if (!mRenderQtImg.isNull())
{
glLoadIdentity();
glPushMatrix();
{
if (mResizedImg.width() <= 0)
{
if (mRenderWidth == mRenderQtImg.width() && mRenderHeight == mRenderQtImg.height())
mResizedImg = mRenderQtImg;
else
mResizedImg = mRenderQtImg.scaled(QSize(mRenderWidth, mRenderHeight),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
glRasterPos2i(mRenderPosX, mRenderPosY);
glPixelZoom(1, -1);
glDrawPixels(mResizedImg.width(), mResizedImg.height(), GL_RGBA, GL_UNSIGNED_BYTE, mResizedImg.bits());
}
glPopMatrix();
glFlush();
}
}
I stole this code from here: https://github.com/Myzhar/QtOpenCVViewerGl
And lastly, here is how my problem looks:
This looks awful.
The image producer should produce new images and emit them through a signal. Since QImage is implicitly shared, it will automatically recycle frames to avoid new allocations. Only when the producer thread out-runs the display thread will image copies be made.
Instead of using an explicit loop in the Camera object, you can run the capture using a zero-duration timer, and having the event loop invoke it. That way the camera object can process events, e.g. timers, cross-thread slot invocations, etc.
There's no need for explicit mutexes, nor for a blocking connection. Qt's event loop provides cross-thread synchronization. Finally, the QtOpenCVViewerGl project performs image scaling on the CPU and is really an example of how not to do it. You can get image scaling for free by drawing the image on a quad, even though that's also an outdated technique from the fixed pipeline days - but it works just fine.
The ASICamera class would look roughly as follows:
// https://github.com/KubaO/stackoverflown/tree/master/questions/asi-astro-cam-39968889
#include <QtOpenGL>
#include <QOpenGLFunctions_2_0>
#include "ASICamera2.h"
class ASICamera : public QObject {
Q_OBJECT
ASI_ERROR_CODE m_error;
ASI_CAMERA_INFO m_info;
QImage m_frame{640, 480, QImage::Format_RGB888};
QTimer m_timer{this};
int m_exposure_ms = 0;
inline int id() const { return m_info.CameraID; }
void capture() {
m_error = ASIGetVideoData(id(), m_frame.bits(), m_frame.byteCount(),
m_exposure_ms*2 + 500);
if (m_error == ASI_SUCCESS)
emit newFrame(m_frame);
else
qDebug() << "capture error" << m_error;
}
public:
explicit ASICamera(QObject * parent = nullptr) : QObject{parent} {
connect(&m_timer, &QTimer::timeout, this, &ASICamera::capture);
}
ASI_ERROR_CODE error() const { return m_error; }
bool open(int index) {
m_error = ASIGetCameraProperty(&m_info, index);
if (m_error != ASI_SUCCESS)
return false;
m_error = ASIOpenCamera(id());
if (m_error != ASI_SUCCESS)
return false;
m_error = ASIInitCamera(id());
if (m_error != ASI_SUCCESS)
return false;
m_error = ASISetROIFormat(id(), m_frame.width(), m_frame.height(), 1, ASI_IMG_RGB24);
if (m_error != ASI_SUCCESS)
return false;
return true;
}
bool close() {
m_error = ASICloseCamera(id());
return m_error == ASI_SUCCESS;
}
Q_SIGNAL void newFrame(const QImage &);
QImage frame() const { return m_frame; }
Q_SLOT bool start() {
m_error = ASIStartVideoCapture(id());
if (m_error == ASI_SUCCESS)
m_timer.start(0);
return m_error == ASI_SUCCESS;
}
Q_SLOT bool stop() {
m_error = ASIStopVideoCapture(id());
return m_error == ASI_SUCCESS;
m_timer.stop();
}
~ASICamera() {
stop();
close();
}
};
Since I'm using a dummy ASI API implementation, the above is sufficient. Code for a real ASI camera would need to set appropriate controls, such as exposure.
The OpenGL viewer is also fairly simple:
class GLViewer : public QOpenGLWidget, protected QOpenGLFunctions_2_0 {
Q_OBJECT
QImage m_image;
void ck() {
for(GLenum err; (err = glGetError()) != GL_NO_ERROR;) qDebug() << "gl error" << err;
}
void initializeGL() override {
initializeOpenGLFunctions();
glClearColor(0.2f, 0.2f, 0.25f, 1.f);
}
void resizeGL(int width, int height) override {
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, height, 0, 0, 1);
glMatrixMode(GL_MODELVIEW);
update();
}
// From http://stackoverflow.com/a/8774580/1329652
void paintGL() override {
auto scaled = m_image.size().scaled(this->size(), Qt::KeepAspectRatio);
GLuint texID;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glGenTextures(1, &texID);
glEnable(GL_TEXTURE_RECTANGLE);
glBindTexture(GL_TEXTURE_RECTANGLE, texID);
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGB, m_image.width(), m_image.height(), 0,
GL_RGB, GL_UNSIGNED_BYTE, m_image.constBits());
glBegin(GL_QUADS);
glTexCoord2f(0, 0);
glVertex2f(0, 0);
glTexCoord2f(m_image.width(), 0);
glVertex2f(scaled.width(), 0);
glTexCoord2f(m_image.width(), m_image.height());
glVertex2f(scaled.width(), scaled.height());
glTexCoord2f(0, m_image.height());
glVertex2f(0, scaled.height());
glEnd();
glDisable(GL_TEXTURE_RECTANGLE);
glDeleteTextures(1, &texID);
ck();
}
public:
GLViewer(QWidget * parent = nullptr) : QOpenGLWidget{parent} {}
void setImage(const QImage & image) {
Q_ASSERT(image.format() == QImage::Format_RGB888);
m_image = image;
update();
}
};
Finally, we hook the camera and the viewer together. Since the camera initialization may take some time, we perform it in the camera's thread.
The UI should emit signals that control the camera, e.g. to open it, start/stop acquisition, etc., and have slots that provide feedback from the camera (e.g. state changes). A free-standing function would take the two objects and hook them together, using functors as appropriate to adapt the UI to a particular camera. If adapter code would be extensive, you'd use a helper QObject for that, but usually a function should suffice (as it does below).
class Thread : public QThread { public: ~Thread() { quit(); wait(); } };
// See http://stackoverflow.com/q/21646467/1329652
template <typename F>
static void postToThread(F && fun, QObject * obj = qApp) {
QObject src;
QObject::connect(&src, &QObject::destroyed, obj, std::forward<F>(fun),
Qt::QueuedConnection);
}
int main(int argc, char ** argv) {
QApplication app{argc, argv};
GLViewer viewer;
viewer.setMinimumSize(200, 200);
ASICamera camera;
Thread thread;
QObject::connect(&camera, &ASICamera::newFrame, &viewer, &GLViewer::setImage);
QObject::connect(&thread, &QThread::destroyed, [&]{ camera.moveToThread(app.thread()); });
camera.moveToThread(&thread);
thread.start();
postToThread([&]{
camera.open(0);
camera.start();
}, &camera);
viewer.show();
return app.exec();
}
#include "main.moc"
The GitHub project includes a very basic ASI camera API test harness and is complete: you can run it and see the test video rendered in real time.

Smooth drawing with QPainter

How can I smoothly grow an angle/length-changing Arc with Qt QPainter? Here is minimum code I just created from Qt's Analog Clock Window Example.
The code randomly changes m_value +- 5 in 50-millisecond. This is to simulate the actual behavior I want to achieve. The arc starts at 12 O'clock position and grow counter-clockwise. m_value is scaled to fit 360 degree (12 O'clock to 12 O'clock).
My goal is to smoothly change the length of arc, in real-time, in response to the (simulated) value given, regardless of input value jitters.
I want to accomplish 2 things:
Smooth redraw of the arc. The current code directly redraw the
value at the time. I does not even use sub angle value. The result
is visually noisy at the end of the arc.
Update the drawing in along with V-Sync. So that I don't waste
computation power for non-displayed redraw. I don't know how to
trigger render event by V-Sync, so I've setup 33-millisecond
timer. This is needed when m_value changes in less than 30 msec.
What I don't want
QtQuick. I'm looking for QPainter way to do it.
Platform I'm using:
Qt 5.x
on Debian Linux (If it matters)
#include <QtGui>
#include "rasterwindow.h"
class SmoothArc : public RasterWindow
{
public:
SmoothArc();
protected:
void timerEvent(QTimerEvent *) Q_DECL_OVERRIDE;
void render(QPainter *p) Q_DECL_OVERRIDE;
private:
int m_timerId;
int m_valueTimerId;
int m_value = 50;
};
SmoothArc::SmoothArc()
{
setTitle("Smooth Arc");
resize(200, 200);
m_timerId = startTimer(33);
m_valueTimerId = startTimer(100);
}
void SmoothArc::timerEvent(QTimerEvent *event)
{
if (event->timerId() == m_timerId)
renderLater();
if (event->timerId() == m_valueTimerId) {
m_value = m_value + (qrand() % 11 - 5);
if (m_value > 100) m_value = 100;
if (m_value < 0) m_value = 0;
}
}
void SmoothArc::render(QPainter *p)
{
p->setRenderHint(QPainter::Antialiasing);
int side = qMin(width(), height());
p->scale(side / 200.0, side / 200.0);
QRectF rect(10, 10, 180, 180);
QPen pen = p->pen();
pen.setWidth(10);
p->setPen(pen);
p->drawArc(rect, 90*16, (360*(m_value/100.0))*16);
}
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
SmoothArc arc;
arc.show();
return app.exec();
}
Complete code is at https://github.com/yashi/smooth-arc. Usual build process like the following should work.
git clone https://github.com/yashi/smooth-arc.git
cd smooth-arc
qmake
make
./smooth-arc
I'm not familiar with the Qt Gui-only approach to this problem, so I'll show how to do it with Qt Widgets instead.
Smooth redraw of the arc. The current code directly redraw the value at the time. I does not even use sub angle value. The result is visually noisy at the end of the arc.
You can use Qt's animation framework to interpolate property changes:
#include <QtWidgets>
class SmoothArc : public QWidget
{
Q_OBJECT
Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged)
public:
SmoothArc();
qreal value() const;
void setValue(qreal value);
signals:
void valueChanged();
protected:
void timerEvent(QTimerEvent *) Q_DECL_OVERRIDE;
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
private:
int m_valueTimerId;
qreal m_value;
QPropertyAnimation m_animation;
};
SmoothArc::SmoothArc()
{
resize(200, 200);
m_valueTimerId = startTimer(100);
m_value = 50;
m_animation.setTargetObject(this);
m_animation.setPropertyName("value");
}
qreal SmoothArc::value() const
{
return m_value;
}
void SmoothArc::setValue(qreal value)
{
if (qFuzzyCompare(value, m_value))
return;
m_value = value;
update();
emit valueChanged();
}
void SmoothArc::timerEvent(QTimerEvent *event)
{
if (event->timerId() == m_valueTimerId) {
qreal newValue = m_value + (qrand() % 11 - 5);
if (newValue > 100) newValue = 100;
if (newValue < 0) newValue = 0;
if (m_animation.state() == QPropertyAnimation::Running)
m_animation.stop();
m_animation.setStartValue(m_value);
m_animation.setEndValue(newValue);
m_animation.start();
}
}
void SmoothArc::paintEvent(QPaintEvent *)
{
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
int side = qMin(width(), height());
p.scale(side / 200.0, side / 200.0);
QRectF rect(10, 10, 180, 180);
QPen pen = p.pen();
pen.setWidth(10);
p.setPen(pen);
p.drawArc(rect, 90*16, (360*(m_value/100.0))*16);
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
SmoothArc arc;
arc.show();
return app.exec();
}
#include "main.moc"
Update the drawing in along with V-Sync. So that I don't waste computation power for non-displayed redraw. I don't know how to trigger render event by V-Sync, so I've setup 33-millisecond timer. This is needed when m_value changes in less than 30 msec.
I think that Qt should handle this for you if you use update():
Updates the widget unless updates are disabled or the widget is hidden.
This function does not cause an immediate repaint; instead it schedules a paint event for processing when Qt returns to the main event loop. This permits Qt to optimize for more speed and less flicker than a call to repaint() does.
Calling update() several times normally results in just one paintEvent() call.

Scaling a QGrahicsView when MouseWheenTurned first scrolls, then scales

I'm trying to add zoom in/out functionality to a graphic I'm drawing in Qt.
What I first did was extend QGraphicsScene with my own class GraphicsScene and overload the wheel event.
class GraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
GraphicsScene(QObject *parent, bool drawAxes){ /*drawing stuff here.. */}
virtual void wheelEvent(QGraphicsSceneWheelEvent *mouseEvent);
signals:
void mouseWheelTurned(int);
};
void GraphicsScene::wheelEvent(QGraphicsSceneWheelEvent* mouseEvent) {
int numDegrees = mouseEvent->delta() / 8;
int numSteps = numDegrees / 15; // see QWheelEvent documentation
emit mouseWheelTurned(numSteps);
}
When the wheel is turned, an event is sent to the view which contains the scene, and there a scale is performed:
class GraphicsView : public QGraphicsView{
Q_OBJECT
qreal m_currentScale;
public:
GraphicsView(QWidget * parent): QGraphicsView(parent){ m_currentScale = 1.0; }
public slots:
void onMouseWheelTurned (int);
};
void GraphicsView::onMouseWheelTurned(int steps) {
qreal sign = steps>0?1:-1;
qreal current = sign* pow(0.05, abs(steps));
if(m_currentScale+current > 0){
m_currentScale += current;
QMatrix matrix;
matrix.scale(m_currentScale, m_currentScale);
this->setMatrix(matrix);
}
}
This works, but I noticed if I zoom in a lot, for example to the top of the graphic, so that the graphic is no longer fully in the viewport, and then I zoom out, the program first scrolls to the botton of the graphic. I can see the vertical scrollbar sliding down. Only when it has reached bottom, does it start to zoom out. What could be the problem?
I would want to zoom in/ out without this scroll up / down behaviour.
You'd be better off handling this in the Scene and sending an event to the View.
Simply handle the event directly in the view with QGraphicsView::wheelEvent‌​, then call its scale function.