Distinguish between Single Touch and MultiTouch
-
Hi everyBody!
I have a QWidget CImagePreview class which has a QGraphicsScene object (with a pixmap inside to display images) and a CMyGraphicsView (subclassed from QGraphicsView and with the setAttribute(WA_AcceptTouchEvent) where i reimplemented the bool viewportEvent(QEvent *event) and handle touch with a swtich case on the event->type().
I receive Qt::TouchBegin, Qt::TouchUpdate and Qt::TouchEnd, the only problem is that i cannot distinguish, when the first Qt::TouchBegin arrives, between single (i want to perform pan and move) or a multi touch (i want to pinchzoom). On the first attempt i achieved the goal to have single touch click like mouse, single touch pan&zoom and multi tuch pinchzoom but then my project structure changed and the problems arrive...
On my first version the structure of the program was like this:QMainWindow mainwindow
---QWidget centralWidget
-------QWidget CImagePreview
----------CMyGraphicsView
And everything works great! inside CMyGraphicsView::viewportEvent(QEvent* event) on TouchBegin and TouchEnd i called QGraphicsView::viewportEvent(event), on Qt::TouchUpdate instead:if(touchPoints.count() == 2) { // calculate scale factor by dividing the length between the two fingers at the end of the gesture // with the length at the start. ( 0 < zoomOut < 1 ) and ( 1 <= zoom in < inf ) QTouchEvent::TouchPoint touchPoint0 = touchPoints.first(); QTouchEvent::TouchPoint touchPoint1 = touchPoints.last(); qreal endLength = static_cast<qreal> (QLineF(touchPoint0.pos(), touchPoint1.pos()).length()); qreal startLength = static_cast<qreal> (QLineF(touchPoint0.startPos(), touchPoint1.startPos()).length()); qreal currentScaleFactor = 1; if(startLength > 0) currentScaleFactor = endLength / startLength; //TOQ:Could startLength be equal to zero in some situations? if (touchEvent->touchPointStates() & Qt::TouchPointReleased) { // if one of the fingers is released, remember the current scale // factor so that adding another finger later will continue zooming // by adding new scale factor to the existing remembered value. m_nTotalScaleFactor *= currentScaleFactor; currentScaleFactor = 1; } // Delegate zoom check and scale to proper function bool res = checkAndScale(m_nTotalScaleFactor * currentScaleFactor); if(!res) qDebug("Zoom out exceed!"); // Return true because i'm the last one who use this event return true; // return QGraphicsView::viewportEvent(event); } else { //Just one finger or more than two, means "pan&move gesture or other", so pass it to the parent class bool res = false; res = QGraphicsView::viewportEvent(event); return res;
Then i had the need to flip (180°) my QWidget... so i read the documentation and find out that i had to use QGraphicsProxyWidget so i re design my structure like these:
QMainWindow mainwindow
---QWidget centralWidget
-------CMyGraphicsView mainwindow_graphicsView
----------QWidget CImagePreview
--------------CMyGraphicsView imagePreview_graphicsVIewIn the mainwindow QGraphicsScene i addwidget(CIMagePreview) and collect the QGraphicsProxyWidget pointer, than i setAcceptTouchEvent(true) function on it and the rotate(180) function.
Unfortunately the touch event didn't arrive anymore to my imagePreview_graphicsVIew (it stops at MainWindow scene) and i was forced to re implement the QWidget CImagePreview::event(QEvent *event) function to capture Touch. From these class i call on his view (only when touch event arrives) ui->imagePreview_graphicsView-> CMyGraphicsView::viewportEvent(event).
But after the Qt::TouchBegin arrives, no more Qt::TouchUpdate or Qt::TouchEnd arrives and the pinch zoom doesn't work but the pan&move with one finger yes.
I noticed that If on CImagePreview::event() i return immediately true in TouchBegin case and, on TouchUpdate and TouchEnd, the CMyGraphicsView::viewportEvent(), i can correctly pinch-zoom but no single click is performed.
I was wondering if there is a way to know on the first Qt::TouchBegin if it's a Begin of a single or multi Touch...
Or a more general question: How is the correct way to handle single/multi touch on these nested structure? (a QGraphicsView which display a scene inside a QWidget, wrapped by QGraphicsProxyWidget, inside the mainWindow scene viewed by another QGraphicsView)
Flipping the QWidget depends on a value read from a file and it is a constraint of the project!
Thanks to everybody for any advice -
OK, i've edited the code, now the mainwindow scene and view are standard QGraphicsScene and QGraphicsView; i start to save the last current Touch event arrived, and insert and if statment in CImagePreview::event(QEvent *event) method; i noticed that when i pass the QEvent::TouchBegin to CMyGraphicsView::viewportEvent(event)->QGraphicsView::viewportEvent(event) the pan&move feature works, but no TouchUpdate arrives and so no zoom are performed..but if i don't call the view method on touchBegin and, instead, immediately return true, the TouchUpdate correctly arrives followeb by and End. Now both pan&Move and PinchZoom works but it is not smooth as before..
These are the actual two methods:bool CMyGraphicsView::viewportEvent(QEvent *event) { QEvent::Type tipo = event->type(); switch (tipo) { case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: { event->accept(); QTouchEvent *touchEvent = dynamic_cast<QTouchEvent*> (event); if(touchEvent == nullptr) return QGraphicsView::viewportEvent(event); QList<QTouchEvent::TouchPoint> touchPoints = touchEvent->touchPoints(); if(touchPoints.count() >= 2) { // calculate scale factor by dividing the length between the two fingers at the end of the gesture // with the length at the start. ( 0 < zoomOut < 1 ) and ( 1 <= zoom in < inf ) QTouchEvent::TouchPoint touchPoint0 = touchPoints.first(); QTouchEvent::TouchPoint touchPoint1 = touchPoints.last(); qreal endLength = static_cast<qreal> (QLineF(touchPoint0.pos(), touchPoint1.pos()).length()); qreal startLength = static_cast<qreal> (QLineF(touchPoint0.startPos(), touchPoint1.startPos()).length()); qreal currentScaleFactor = 1; if(startLength > 0) currentScaleFactor = endLength / startLength; if (touchEvent->touchPointStates() & Qt::TouchPointReleased) { // if one of the fingers is released, remember the current scale // factor so that adding another finger later will continue zooming // by adding new scale factor to the existing remembered value. m_nTotalScaleFactor *= currentScaleFactor; currentScaleFactor = 1; } // Delegate zoom check and scale to proper function bool res = checkAndScale(m_nTotalScaleFactor * currentScaleFactor); if(!res) qDebug("Zoom out exceed!"); // Return true because i'm the last one who use this event return true; } else { //Just one finger or more than two, means "grab&move gesture or other", so pass it to the parent class bool res = QGraphicsView::viewportEvent(event); return res; } } default: break; } bool res = QGraphicsView::viewportEvent(event); return res; }
And CIMagePreview:
bool CImagePreview::event(QEvent *event) { QEvent::Type tipo = event->type(); switch (tipo) { case QEvent::TouchBegin: { event->accept(); qDebug("ImagePreview:Begin"); if(event->type() == m_eLastEventType) // Multitouch in atto porcatroia samir! { qDebug("Multi Touch"); m_eLastEventType = event->type(); return true; } bool res1 = ui->graphicsView_imagePreview->viewportEvent(event); m_eLastEventType = event->type(); return res1; } case QEvent::TouchUpdate: { event->accept(); qDebug("ImagePreview:update"); bool res2 = ui->graphicsView_imagePreview->viewportEvent(event); m_eLastEventType = event->type(); return res2; break; } case QEvent::TouchEnd: { event->accept(); qDebug("ImagePreview:End"); bool res3 = ui->graphicsView_imagePreview->viewportEvent(event); m_eLastEventType = event->type(); return res3; } default: break; } return QWidget::event(event); }
I still don't understand why if i pass the QEvent::TouchBegin to the QGraphicsView (the father of CMyGraphicsView) no more Touch events arrives (Update and End)...even if i return true and accept the event in the CMyGraphicsVIew function..
So my line of reasoning is:
Every time a touch event arrives i store the value in a variable named m_eLastEventType.
Arrives a TouchBegin: is the last event type Begin? NO: Threat it like a single touch and pass it to QGraphicsView YES: multi touch detected, return true and don't call QGraphicsView::viewportEvent(event).
The problem is that when i perform a single click, only the TouchBegin arrives, and no TouchEnd..so if I click two times, with one finger, the second click is read as multi touch... -
@QTad
Touch events are pretty low-level. A multitouch gesture might look something like this:TouchBegin [TouchPoints has 1 entry]
TouchUpdate [TouchPoints has 1 entry]
TouchUpdate [TouchPoints has 1 entry]
TouchUpdate [TouchPoints has 2 entries] // 2nd finger started
TouchUpdate [TouchPoints has 2 entries]
TouchUpdate [TouchPoints has 2 entries]
TouchUpdate [TouchPoints has 2 entries]
TouchUpdate [TouchPoints has 2 entries]
TouchUpdate [TouchPoints has 1 entry] // One finger left the screen
TouchEndBy using the TouchPoint's id, you can follow each finger separately.
So, you cannot know at the TouchBegin whether it's going to be a single or multi touch sequence. You have to wait a bit and decide at some point.Combining QGraphicsProxyWidget and Gestures sounds painful. I use both (gesture handling and proxy widgets), but so far I never had reason to combine both. There might be unresolved bugs, because QGraphicsProxyWidget is highly complex, and does not have high priority within Qt's development.
If there is any way you can get rid of the ProxyWidget and do whatever you need to do inside GraphicsView, I would go for that. -
Thank you very much for your answer!
I must use QGraphicsProxyWidget because i need to flip the application of 180°! I'm in an embedded system with petalinux where i can't use a window system like x11 and so the rotation has to take place at a higher level (in the qt code)...
The previous code with the if statement works quite well...sometimes it doesn't receive the multitouch but it's decent! I just don't understand why if i pass the TouchBegin to QGraphicsVIew::viewportEvent(event) no TouchUpdate arrives...instead if i return true TouchUpdate and End arrive...