Touch support in Qt?

Posted by Bradley T. Hughes on April 20, 2009 · 16 comments

A few weeks ago, Denis blogged about some thoughts he had on how gesture recognition could work in Qt. One of the things he mentioned, and that was also brought up in the comments, was support for multi-point touch gestures. As a MacBook Pro owner, I already use these gestures every single day. Needless to say, he got my attention. I would certainly like to see multi-point touch events and gestures in Qt so that I can start using them myself. The only question is… how do we do it?

Before answering that question, I think we need to look at 2 other important questions. The answers to these will affect how we design the API for adding touch support to Qt.

What is multi-point touch?

We’re already seeing consumer multi-point touch devices that you can buy today. Apple has the iPhone and iPod touch, Dell and HP have multi-point touch Tablet PCs. There are things like the DiamondTouch table top as well. Have you seen the TED Talk video demoing multi-point touch using only the IR sensor on a Wii remote?

There are many differences, form factor being the biggest, but there are some commonalities between the above examples.

  1. Single input device that reports multiple input/contact points.
  2. Coordinates are reported in absolute coordinates.
  3. User input is superimposed over the display (i.e. the user interacts directly with the screen).

What is multi-point touch not?

An equally important question that needs to be answered. I feel I can safely say that multi-point touch is not multiple devices reporting single points (i.e. two mice != multi-point touch). Peter Hutterer wrote a good summary of the difference between multi-point touch, multi-pointer, and multi-device.

One gray area is for devices that report relative cursor position changes (like the trackpad on my MacBook Pro). Such devices tend not to be attached to the display (so the input is not superimposed). But it should still be possible to get multi-point touch events from this kind of device and feed it into a gesture recognizer. This will require more thought and testing.

Ideas for a Touch API in Qt

After thinking about the above, and also have a look at the Windows 7 and iPhone SDK, we’ve come up with the idea of having one event (call it QTouchEvent for now) that contains a list of all the touch points reported by the device. Each touch point would have a state (pressed, moved, stationary, released), a unique identifier, and a position (both in widget local and global coordinates). I like the QGraphicsSceneMouseEvent API that includes the previously reported and starting positions, so I would say those are a must in the API. Other information may or may not be reported by the device as well, for example pressure.

So, we end up with something like this:

namespace Qt {
    enum TouchPointState {
        TouchPointPressed,
        TouchPointMoved,
        TouchPointStationary,
        TouchPointReleased
    };
}

class QTouchEventTouchPointPrivate;
class Q_GUI_EXPORT QTouchEvent : public QInputEvent
{
public:
    class Q_GUI_EXPORT TouchPoint
    {
    public:
        TouchPoint(int id = -1);
        ~TouchPoint();

        int id() const;

        Qt::TouchPointState state() const;

        const QPointF &pos() const;
        const QPointF &startPos() const;
        const QPointF &lastPos() const;

        const QPointF &globalPos() const;
        const QPointF &startGlobalPos() const;
        const QPointF &lastGlobalPos() const;

        qreal pressure() const;

    private:
        QTouchEventTouchPointPrivate *d;

        friend class QApplicationPrivate;
    };

    QTouchEvent(QEvent::Type type, Qt::KeyboardModifiers modifiers, const QList<TouchPoint *> &touchPoints);

    inline const QList<TouchPoint *> &touchPoints() const { return _touchPoints; }

protected:
    QList<TouchPoint *> _touchPoints;

    friend class QApplicationPrivate;
};

The QGraphicsView framework would also have a QGraphicsSceneTouchEvent that looked almost identical, the only difference being that the global coordinate functions would be replaced by scene and screen coordinate functions.

So that means that with a simple addition, the scribble example could be modified to support multi-point touch painting by doing something like this:

bool ScribbleArea::event(QEvent *event)
{
    switch (event->type()) {
    case QEvent::TouchBegin:
    case QEvent::TouchUpdate:
    case QEvent::TouchEnd:
    {
        QList<QTouchEvent::TouchPoint *> touchPoints = static_cast<QTouchEvent *>(event)->touchPoints();
        foreach (QTouchEvent::TouchPoint *touchPoint, touchPoints) {
            switch (touchPoint->state()) {
            case Qt::TouchPointStationary:
                // don't do anything if this touch point hasn't moved
                continue;
            default:
                {
                    int diameter = int(50 * touchPoint->pressure());
                    QRectF rectF(0, 0, diameter, diameter);
                    rectF.moveCenter(touchPoint->pos());
                    QRect rect = rectF.toRect();

                    QPainter painter(&image);
                    painter.setPen(Qt::NoPen);
                    painter.setBrush(myPenColors.at(touchPoint->id()));
                    painter.drawEllipse(rectF);
                    painter.end();

                    modified = true;
                    int rad = 2;
                    update(rect.adjusted(-rad,-rad, +rad, +rad));
                }
                break;
            }
        }

        event->accept();
        return true;
    }
    default:
        break;
    }
    return QWidget::event(event);
}

Other things to consider

First off, what about existing code? Adding a new event type for a new input device type means that none of the existing widgets out there will support touch events at the time of release. The solution here, as I see it, is simple. We do the same thing that we did with tablet support and send mouse events to the widget that don’t handle the touch events. We could even go a step further and say that we only send touch events to widgets and QGraphicsItems that explicitly enable touch event support (we could add a function or widget attribute to do this). I kind of like this idea, so that we don’t spend time sending events to widgets that usually won’t accept them.

Second, what about interleaving mouse events and touch events? Should the API allow a widget to receive both touch events and mouse events at the same time? I don’t think so. I am inclined to say that a widget that accepts the first touch event should only get touch events after that, otherwise it gets only mouse events.

Lastly, what about touch events that affect multiple widgets or QGraphicsItems? For example, what if there was an application with a big, round volume knob that you rotate with 2 fingers, but your second finger happens to press the screen just outside of the knob’s shape? And what if there happens to be another item or widget under that second touch point? I can certainly see this happening on a small screen, or on Tablet PCs that have lots of elements on the screen. I’m unsure of what to do here, so for now I’m going to say that perhaps the best thing to do for now is to say that the item or widget under the first touch point gets ALL touch points, regardless of where they may be. This means that you couldn’t, for example, use 2 hands to turn 2 separate knobs on such a knob (if this was some sort of DJ mixing application, for example). Some prototyping and testing will be necessary here to see what we decide to do.

There are probably other things to think about that I’ve not covered here. Please let me know if you can think of any, or if you have any good ideas and comments on the above thoughts, API, assumptions, etc.

Hopefully we can get something working in Qt at some point so that I can finally stop getting frustrated with my Linux laptop when my two- (scroll) and three-finger (back, forward) gestures don’t work… :P

QShare(this)

No related posts.


16 comments

1 Wizard Of April 20, 2009 at 5:05 pm
 

Developers should be able to further abstract the TouchPoint input data. In the examples above, it seems that the raw coordinates are processed. This is something that should be done centrally in an application. For this I would propose to introduce a Gesture class which can match a path of TouchPoints over time. If a match occurs, an event is fired by the gesture. In multi-touch specific applications, this Gesture event could be processed and acted upon directly whereas the event could be further mapped down to traditional mouse or keyboard events for legacy applications.

For example, if two TouchPoints approach each other within a given amount of time, the “zoom out” Gesture defined by the developer would match that input and trigger an event.

2 Will Stephenson April 20, 2009 at 5:08 pm
 

Nice start, but what about some sugar to do with identifying changed touch points across distinct touchevents? Suppose I do the often-demo’d two-finger grabbing the corners of a photo, which is then transformed to fit between my fingers, and release one point. As an app developer I’d like to react to that and I can imagine a lot of duplicate code in apps iterating the touchPoints() to identify which changed, were released, are new etc.

3 Felix April 20, 2009 at 5:10 pm
 

Speaking of gestures and the MacBook Pro – how would your API map already existing multitouch gestures such as Swipe, Magnify, Rotate? Those are single events, you do not get the actual finger positions but gesture values like a single magnification value…

4 zbenjamin April 20, 2009 at 5:12 pm
 

Hey Bradley , nice Api as usual. I like how it would work.
@Wizard I simply would suggest a API like QKeySequence so you can check that in the event Handler. Totally removing raw Data Handling maybe would remove some use cases.

QTouchSequence seq;

bool ScribbleArea::event(QEvent *event)
{
switch (event->type()) {
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
case QEvent::TouchEnd:
{
QList touchPoints = static_cast(event)->touchPoints();
if(seq.matches(touchPoints))
// do some stuff
………………….

}

}

5 enjahova April 20, 2009 at 7:35 pm
 

I recommend looking into NUI Group. They have already done a lot of (OSS) work with MultiTouch and if anyone is standardizing it, I put my money on them:
http://nuigroup.com/

6 paulpach April 20, 2009 at 7:59 pm
 

I think what you described looks good, but I also think we need a way to abstract this further and make it easier to use.

This might be the thing Wizard was talking about, but there are not enough details to be sure.

How about a hierarchy of gestures:

QGesture
QZoomGesture
QRotateGesture
QBackGesture
QForwardGesture
QScrollGesture
… who-knows-what-else-gesture

The QGesture class would have 2 signals:
begin()
end()

Then each one of the gesture classes, could have additional signals depending on what they do. For example, QZoomGesture would have a signal:
zoom(float)
with the value 0.5 for zooming out a factor of 50%, or 2.0 to zoom in by a factor of 200%
As the user is pinching, the gesture objects issues the begin() signal, then a bunch of of zoom(float) signals while the user holds the fingers, and when the user releases, it gives the end() signal.

To use the gestures, we would just add them to a widget, and connect the signals/slots

Note that this api is very high level and is independent of multi-touch. Some could work with single touch even, like the back gesture. A widget can register all the gestures it wants and some of them like the zoom will simply not be triggered under a single touch device.

For example, if I want to have my widget respond to zoom gestures:

MyWidget::MyWidget( QWidget *parent, char *name ) : …
{
QGesture *zoomGesture = new QZoomGesture(this);

QLCDNumber *lcd = new QLCDNumber( this );
QSlider *s = new QSlider( QSlider::Horizontal, this );

connect( zoomGesture, SIGNAL(zoom(float)), this, SLOT(zoom(float)) );
}

void MyWidget::zoom(float zoomLevel)
{
// zoom in or out according to zoom level
}

This code would compile and run regardless of multitouch, it would simply never trigger if multitouch is not available.

Some widgets could even have some of these gestures by default, for example Marble, a knob widget, a QWebWidget, etc…

7 Wizard Of April 20, 2009 at 8:19 pm
 

I wasn’t requesting raw handling to be removed, just that there should be a higher-level handling, too. Thanks for your efforts.

8 Ivan April 20, 2009 at 8:26 pm
 

Incredible!
Today I started my own implementation of custom touch events and event handlers for a multitouch project! We are already sharing lots of common ideas.
I am looking forward to official touch support in QT and repository where to contribute.
This would be of great help for the Maemo people, as AFAIK there isn’t multitouch support yet.

9 hansk April 20, 2009 at 8:27 pm
 

I’ve integrated multi-touch into one of our Qt applications. Here’s a short video demonstrating some interaction:

http://www.youtube.com/watch?v=Y9qDXTJvlXk

In the implementation I created a set of QEvent based touch event classes. Also, a set of QEvent based gesture classes and a gesture filter class that injected the gesture events. Each touch event contains a minimum of:

- a unique “touch” id so the event is matched to the correct touch.
- state: press, move, release
- pressure (can be emulated if by touch area)

Also, I highly recommend that you support interleaving events from multi-touch, mouse, pen. I do this so as to allow a primary input device, such as a pen, to drive the interaction and the touch events to modify the operation. For example, the pen used for drawing while a touch panning the view. In the case of a control responding to touch vs mouse, it can be handled within the logic of the control. If the press begins with a touch, then track only touch events with touch id.

Using two fingers to rotate a knob, as in your example, is not what I would expect. Instead i would simply let a single touch rotate. But I understand the issue you are trying the explain. Even in that case it would make no sense supporting multi-touch but not allowing a user to “touch” multiple knobs. That is the beauty of multi-touch!

If that two finger interaction you describe occurred on a real device, both knobs would be controlled. If the knobs are too small and cause errors in touch then the UI needs to be redesigned or repositioned.

The introduction of multi-touch into the UX opens up a whole box of issues. But it sure is fun.

10 mycall April 20, 2009 at 9:31 pm
 

You should consider having a pressedValue for QTouchEvent to support aftertouch (e.g. MIDI aftertouch).

11 David April 20, 2009 at 9:56 pm
 

I’ve seen a video recently about a multitouch screen with pressure sensitivity also.
It gets pressure data even before that the finger touches the screen (but very close to it).
Maybe you should include pressure data with the touch event.

12 Bradley T. Hughes April 21, 2009 at 7:34 am
 

@Wizard, zbenjamin, paulpach: What I’ve described here is just how the API for (multi-)touch would look. As described in Denis’s blog, we are also looking at ways of having nice high level ways of detecting gestures. Last I spoke to Denis, we were going to use an event filter approach in each gesture recognizer, and the recognizers would generate the high-level gesture events.

@Will: Have any idea on how that sugar would look? :) Indeed, I see alot of people potentially writing code that iterates over all of the touch points all the time, but I don’t really see another way of doing it. Other than sending each touch point as a separate event, I don’t know how to flag only the stuff that changed (and I really don’t want to send one event per touch point, it’s inefficient and causes lots of people to duplicate the logic to collect all the touch info into one place when writing widgets and/or gesture recognizers).

@Felix: I am assuming that Denis’s gesture API would look for the platform specific events from Mac OS X (and Windows 7, for that matter) and translate those into gesture events. It’s not something that would be covered by this API though…

@enjahova: reading now ;)

@Ivan: We’re hoping to get something up and running on Windows 7 soon (which seems to really be the only thing available at the moment that can easily allow us to try multi-touch), so once we get our repositories opened up, I will push what we have.

@hansk: Regarding interleaving, yes, we will support interleaving if the events come from different devices. I certainly don’t want to limit that. But what I don’t want is, for example, for the widget to accept the first touch event (so the widget doesn’t get a mouse press), then ignore the touch moves (so the widget gets mouse moves with the button pressed without ever receiving the press). So, to be clearer, what I am talking about is that a widget will only ever get either touch events or mouse events from the touch screen, but not both. In fact, I have introduced 3 touch event types that I use to control this behavior: TouchBegin, TouchUpdate, TouchEnd. A widget (or graphics item) has to accept the touch begin to get the other touch events, otherwise it only gets mouse events.

And you are right about not being able to touch multiple elements. I will see what we can do about that. Certainly going to be interesting :P

@mycall: Touch point pressure isn’t enough? One question that I got from a colleague in the hallway the other day was “what about proximity?” This sounds similar… I was thinking that I could add a TouchStateProximity or something similar (so that you could get a “hover” like effect both before and after a press/release sequence).

13 Ivan April 21, 2009 at 7:53 am
 

Sending all touch events at once may be efficient for small scale projects, for example a multitouch tablet screen, but on slightly bigger scale things may become different.
The multitouch project I am working on is a bar for clubs and discos, that would be several square meters and many people may be interacting with different parts of it, which would easily generate decimal number of events. And there would be all the cups and things people are putting on the bar, that would just stay there. It would be really inefficient to send all this touch events every time. As Will said this would be a lot of iterating and comparisons to see what has changed since the last update.
On the other hand the movement of so many points would generate a storm of events if they are sent one by one.

14 ancientcoder April 21, 2009 at 3:01 pm
 

How are you going to deal with focus events?

15 ktangao April 21, 2009 at 10:13 pm
 

There is another point to consider. I am working on a multi-user (table-top based) application using Anoto’s pens ( http://www.anoto.com/ ) as input. Those pens are single input but my application allows to have more than one user. This means i have to give an Id to each pen and process events relatively to the user. This is also possible with Diamond Touch tables ( http://www.merl.com/projects/DiamondTouch/ ). So please, add an Id to the QTouchEvent to recognize the user who sends the event. This will be really useful in multi-user applications!!!

16 David Anderson April 22, 2009 at 5:11 pm
 

Hi, I am working on it, Trying to make exactly the same thing, Make Qt Multi Touch, The first approach was extending every widget, naming it “MQ…”, and overriding the appropriate slots, and creating new ones, this would be very hard and would take a long time, By now i’m trying to make this with Event Filters, Having some progress by now… I Tryed GSoC on the NuiGroup, unfortunately wasn’t accepted, Take a look at my proposal: http://nuigroup.com/?ACT=28&fid=86&aid=2927_R0bTZz6OiwicypMBddU2

I Also created a Google Group for it, which is quite inactive, by the way this is the link: http://groups.google.com.br/group/multiqt

About the widgets that were not multitouch handlers, The touch input would be converted to a regular Mouse event, and the widget would react as it would if it was a mouse event, without affecting its regular action.
I would like to contribute with this, Will regularly come to this.

Well that’s it…

Comments on this entry are closed.

Previous post:

Next post: