~ubuntu-branches/ubuntu/trusty/maliit-framework/trusty-proposed

« back to all changes in this revision

Viewing changes to src/mimrotationanimation.cpp

  • Committer: Package Import Robot
  • Author(s): Iain Lane
  • Date: 2013-01-31 13:26:48 UTC
  • Revision ID: package-import@ubuntu.com-20130131132648-w1u9d2279tppxcft
Tags: upstream-0.94.1
ImportĀ upstreamĀ versionĀ 0.94.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* * This file is part of Maliit framework *
 
2
 *
 
3
 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
 
4
 * All rights reserved.
 
5
 *
 
6
 * Contact: maliit-discuss@lists.maliit.org
 
7
 *
 
8
 * This library is free software; you can redistribute it and/or
 
9
 * modify it under the terms of the GNU Lesser General Public
 
10
 * License version 2.1 as published by the Free Software Foundation
 
11
 * and appearing in the file LICENSE.LGPL included in the packaging
 
12
 * of this file.
 
13
 */
 
14
 
 
15
#include "mimrotationanimation.h"
 
16
#include "qdebug.h"
 
17
#include "mimremotewindow.h"
 
18
#include "mimxapplication.h"
 
19
#include "mpassthruwindow.h"
 
20
#include "mimserveroptions.h"
 
21
 
 
22
#include <QBitmap>
 
23
#include <QDesktopWidget>
 
24
#include <QGraphicsScene>
 
25
#include <QObject>
 
26
#include <QPropertyAnimation>
 
27
#include <QResizeEvent>
 
28
#include <QX11Info>
 
29
 
 
30
#include <X11/Xlib.h>
 
31
#include <X11/Xatom.h>
 
32
#include <X11/Xutil.h>
 
33
 
 
34
SnapshotPixmapItem::SnapshotPixmapItem(QGraphicsItem *parent) :
 
35
        QObject(),
 
36
        QGraphicsPixmapItem(parent)
 
37
{
 
38
}
 
39
 
 
40
SnapshotPixmapItem::SnapshotPixmapItem(QPixmap pixmap, QGraphicsItem *parent) :
 
41
        QObject(),
 
42
        QGraphicsPixmapItem(pixmap, parent)
 
43
{
 
44
}
 
45
 
 
46
SnapshotPixmapItem::~SnapshotPixmapItem() {}
 
47
 
 
48
 
 
49
MImDamageMonitor::MImDamageMonitor(MImRemoteWindow* remoteWin,
 
50
                                   QObject* parent) :
 
51
    QObject(parent),
 
52
    timeoutTimer(),
 
53
    damageDetected(false)
 
54
{
 
55
    timeoutTimer.setInterval(1000);
 
56
    timeoutTimer.setSingleShot(true);
 
57
    connect(&timeoutTimer,SIGNAL(timeout()),
 
58
            this,SLOT(timeoutExpired()));
 
59
 
 
60
    remoteWindowChanged(remoteWin);
 
61
}
 
62
 
 
63
void
 
64
MImDamageMonitor::remoteWindowChanged(MImRemoteWindow *newRemoteWindow)
 
65
{
 
66
    remoteWindow = newRemoteWindow;
 
67
}
 
68
 
 
69
void
 
70
MImDamageMonitor::activate()
 
71
{
 
72
    damageDetected = false;
 
73
    // reset triggers
 
74
    cancel();
 
75
    connect(remoteWindow, SIGNAL(contentUpdated(QRegion)),
 
76
            this, SLOT(contentUpdated(QRegion)));
 
77
}
 
78
 
 
79
void
 
80
MImDamageMonitor::waitForDamage()
 
81
{
 
82
    timeoutTimer.start();
 
83
    if (damageDetected) {
 
84
        qDebug() << __PRETTY_FUNCTION__ << " - damage already received, emitting signal.";
 
85
        cancel();
 
86
        Q_EMIT damageReceivedOrTimeout();
 
87
    }
 
88
}
 
89
 
 
90
void
 
91
MImDamageMonitor::contentUpdated(QRegion)
 
92
{
 
93
    damageDetected = true;
 
94
    // Emit signal only after client called waitForDamage().
 
95
    if (timeoutTimer.isActive()) {
 
96
        qDebug() << __PRETTY_FUNCTION__ << " - damage received, emitting signal.";
 
97
        cancel();
 
98
        Q_EMIT damageReceivedOrTimeout();
 
99
    }
 
100
}
 
101
 
 
102
void
 
103
MImDamageMonitor::cancel()
 
104
{
 
105
    timeoutTimer.stop();
 
106
    if (remoteWindow) {
 
107
        disconnect(remoteWindow, SIGNAL(contentUpdated(QRegion)),
 
108
                   this, SLOT(contentUpdated(QRegion)));
 
109
    }
 
110
}
 
111
 
 
112
void
 
113
MImDamageMonitor::timeoutExpired()
 
114
{
 
115
    qDebug() << __PRETTY_FUNCTION__;
 
116
    cancel();
 
117
    Q_EMIT damageReceivedOrTimeout();
 
118
}
 
119
 
 
120
namespace {
 
121
    qreal rotateAngle(int startAngle, int endAngle) {
 
122
        // Special cases ensuring shortest rotation angle according to original code from
 
123
        // MCrossFadedOrientationAnimationPrivate::setRootElementRotationAnimationValues.
 
124
        if (startAngle == 270 && endAngle == 90) {
 
125
            return 180.0;
 
126
        } else if (startAngle == 180 && endAngle == 0) {
 
127
            return 180.0;
 
128
        } else if (startAngle == 270 && endAngle == 0) {
 
129
            return 90.0;
 
130
        } else if (startAngle == 0 && endAngle == 270) {
 
131
            return -90.0;
 
132
        } else {
 
133
            return endAngle - startAngle;
 
134
        }
 
135
    }
 
136
}
 
137
 
 
138
MImRotationAnimation::MImRotationAnimation(QWidget* snapshotWidget,
 
139
                                           QWidget* passThroughWindow,
 
140
                                           MImXServerLogic *serverLogic,
 
141
                                           const MImServerXOptions &options) :
 
142
        QGraphicsView(new QGraphicsScene(), passThroughWindow),
 
143
 
 
144
        snapshotWidget(snapshotWidget),
 
145
        remoteWindow(0),
 
146
        animationStartPixmapItem(0),
 
147
        animationEndPixmapItem(0),
 
148
        startOrientationAngle(0),
 
149
        currentOrientationAngle(0),
 
150
        aboutToChangeReceived(false),
 
151
        damageMonitor(0),
 
152
        xOptions(options),
 
153
        mServerLogic(serverLogic)
 
154
{
 
155
    // Animation plays on top of a black backround,
 
156
    // covering up the underlying application.
 
157
    setBackgroundBrush(QBrush(Qt::black));
 
158
 
 
159
    // Get rid of Meegotouch decorations.
 
160
    setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
 
161
 
 
162
    // Avoid drawing transparent pixel borders.
 
163
    setFrameShape(QFrame::NoFrame);
 
164
 
 
165
    // We do not want input focus for that window.
 
166
    setAttribute(Qt::WA_X11DoNotAcceptFocus);
 
167
 
 
168
    // Avoid a white flicker when popping up our
 
169
    // extra window for the animation.
 
170
    setAttribute(Qt::WA_NoSystemBackground);
 
171
    setAttribute(Qt::WA_OpaquePaintEvent);
 
172
    viewport()->setAttribute(Qt::WA_NoSystemBackground);
 
173
    viewport()->setAttribute(Qt::WA_OpaquePaintEvent);
 
174
 
 
175
    // Remove all animation elements from scene and hide.
 
176
    connect(&rotationAnimationGroup, SIGNAL(finished()),
 
177
            this, SLOT(clearScene()));
 
178
 
 
179
    connect(mServerLogic, SIGNAL(remoteWindowChanged(MImRemoteWindow*)),
 
180
            this, SLOT(remoteWindowChanged(MImRemoteWindow*)), Qt::UniqueConnection);
 
181
 
 
182
    damageMonitor = new MImDamageMonitor(remoteWindow, this);
 
183
    connect(damageMonitor, SIGNAL(damageReceivedOrTimeout()),
 
184
            this, SLOT(startAnimation()));
 
185
 
 
186
    hide();
 
187
 
 
188
    if (QApplication::desktop()) {
 
189
        resize(QApplication::desktop()->screenGeometry().size());
 
190
    }
 
191
}
 
192
 
 
193
void
 
194
MImRotationAnimation::cancelAnimation()
 
195
{
 
196
    // disableAnimation() slot is connected to passThruWindowUnmapped()
 
197
    // So, in order to be on the safe side. We clean the scene.
 
198
    if (rotationAnimationGroup.state() != QAbstractAnimation::Stopped) {
 
199
        rotationAnimationGroup.stop();
 
200
    }
 
201
 
 
202
    damageMonitor->cancel();
 
203
    clearScene();
 
204
 
 
205
    aboutToChangeReceived = false;
 
206
}
 
207
 
 
208
 
 
209
void
 
210
MImRotationAnimation::resizeEvent(QResizeEvent *event)
 
211
{
 
212
    setSceneRect(QRect(QPoint(0,0),event->size()));
 
213
}
 
214
 
 
215
void
 
216
MImRotationAnimation::setupScene()
 
217
{
 
218
    animationStartPixmapItem = new SnapshotPixmapItem(compositeWindowStart);
 
219
    // See crossfadedorientationanimationstyle.css for the origin of these values.
 
220
    animationStartPixmapItem->setTransformOriginPoint(240,240);
 
221
    animationStartPixmapItem->setPos(0,0);
 
222
    if (scene()) {
 
223
        scene()->addItem(animationStartPixmapItem);
 
224
    }
 
225
}
 
226
 
 
227
void
 
228
MImRotationAnimation::showInitial()
 
229
{
 
230
    show();
 
231
    // We need to be on the screen really fast, otherwise you would
 
232
    // see the meegotouch orientation change in the window under us.
 
233
    // Going through the event loop will take about 800ms, which is too long.
 
234
    // Thus, requesting a synchronous update here.
 
235
    // TODO: Not a perfect solution, in rare cases and high load situations
 
236
    // application is updating the screen faster than meego-im-uiserver.
 
237
    repaint();
 
238
}
 
239
 
 
240
void
 
241
MImRotationAnimation::showEvent(QShowEvent*) {
 
242
    // Setting _NET_WM_WINDOW_TYPE to _NET_WM_WINDOW_TYPE_INPUT
 
243
    // prevents the underlying app from losing focus and
 
244
    // avoid window decorations such as the slide-in animation.
 
245
    static Atom input = XInternAtom(QX11Info::display(), "_NET_WM_WINDOW_TYPE_INPUT", False);
 
246
    static Atom window_type = XInternAtom(QX11Info::display(), "_NET_WM_WINDOW_TYPE", False);
 
247
    XChangeProperty(QX11Info::display(), effectiveWinId(), window_type, XA_ATOM, 32,
 
248
                    PropModeReplace, (unsigned char *) &input, 1);
 
249
}
 
250
 
 
251
void
 
252
MImRotationAnimation::setupAnimation(int fromAngle, int toAngle) {
 
253
 
 
254
    qreal hiddenOpacity = 0;
 
255
    qreal visibleOpacity = 1.0;
 
256
 
 
257
    // See crossfadedorientationanimationstyle.css for the origin of the animation parameters.
 
258
    int duration = 500;
 
259
 
 
260
    QPropertyAnimation* startAnimations[2];
 
261
    QPropertyAnimation* endAnimations[4];
 
262
 
 
263
    startAnimations[0] = new QPropertyAnimation();
 
264
    startAnimations[1] = new QPropertyAnimation();
 
265
    endAnimations[0] = new QPropertyAnimation();
 
266
    endAnimations[1] = new QPropertyAnimation();
 
267
    endAnimations[2] = new QPropertyAnimation();
 
268
    endAnimations[3] = new QPropertyAnimation();
 
269
 
 
270
    int initialRotationOfTarget = fromAngle - toAngle;
 
271
    int rotateBy = rotateAngle(fromAngle, toAngle);
 
272
 
 
273
    QPixmap remoteWindowPixmap;
 
274
    if (remoteWindow) {
 
275
        // This is the live pixmap of the application coming from X.
 
276
        // It stays live if it's used in read-only mode.
 
277
        remoteWindowPixmap = remoteWindow->windowPixmap();
 
278
    }
 
279
    animationEndPixmapItem = new SnapshotPixmapItem(remoteWindowPixmap);
 
280
    animationEndPixmapItem->setPos(0,0);
 
281
    animationEndPixmapItem->setTransformOriginPoint(240,240);
 
282
    animationEndPixmapItem->setRotation(initialRotationOfTarget);
 
283
    animationEndPixmapItem->setOpacity(hiddenOpacity);
 
284
 
 
285
    animationEndVkbOverlayItem = new SnapshotPixmapItem(grabVkbOnly());
 
286
    animationEndVkbOverlayItem->setPos(0,0);
 
287
    animationEndVkbOverlayItem->setTransformOriginPoint(240,240);
 
288
    animationEndVkbOverlayItem->setRotation(initialRotationOfTarget);
 
289
    animationEndVkbOverlayItem->setOpacity(hiddenOpacity);
 
290
 
 
291
    if (scene()) {
 
292
        scene()->addItem(animationEndPixmapItem);
 
293
        scene()->addItem(animationEndVkbOverlayItem);
 
294
    }
 
295
 
 
296
    startAnimations[0]->setPropertyName("rotation");
 
297
    startAnimations[0]->setStartValue(0);
 
298
    startAnimations[0]->setEndValue(rotateBy);
 
299
    startAnimations[0]->setEasingCurve(QEasingCurve::InOutExpo);
 
300
    startAnimations[0]->setDuration(duration);
 
301
 
 
302
    startAnimations[1]->setPropertyName("opacity");
 
303
    startAnimations[1]->setStartValue(visibleOpacity);
 
304
    startAnimations[1]->setEndValue(hiddenOpacity);
 
305
    startAnimations[1]->setEasingCurve(QEasingCurve::InOutExpo);
 
306
    startAnimations[1]->setDuration(duration);
 
307
 
 
308
    endAnimations[0]->setPropertyName("rotation");
 
309
    endAnimations[0]->setStartValue(initialRotationOfTarget);
 
310
    endAnimations[0]->setEndValue(initialRotationOfTarget+rotateBy);
 
311
    endAnimations[0]->setEasingCurve(QEasingCurve::InOutExpo);
 
312
    endAnimations[0]->setDuration(duration);
 
313
 
 
314
    endAnimations[1]->setPropertyName("opacity");
 
315
    endAnimations[1]->setStartValue(hiddenOpacity);
 
316
    endAnimations[1]->setEndValue(visibleOpacity);
 
317
    endAnimations[1]->setEasingCurve(QEasingCurve::InOutExpo);
 
318
    endAnimations[1]->setDuration(duration);
 
319
 
 
320
    // same for vkb overlay
 
321
    endAnimations[2]->setPropertyName("rotation");
 
322
    endAnimations[2]->setStartValue(initialRotationOfTarget);
 
323
    endAnimations[2]->setEndValue(initialRotationOfTarget+rotateBy);
 
324
    endAnimations[2]->setEasingCurve(QEasingCurve::InOutExpo);
 
325
    endAnimations[2]->setDuration(duration);
 
326
 
 
327
    endAnimations[3]->setPropertyName("opacity");
 
328
    endAnimations[3]->setStartValue(hiddenOpacity);
 
329
    endAnimations[3]->setEndValue(visibleOpacity);
 
330
    endAnimations[3]->setEasingCurve(QEasingCurve::InOutExpo);
 
331
    endAnimations[3]->setDuration(duration);
 
332
 
 
333
    startAnimations[0]->setTargetObject(animationStartPixmapItem);
 
334
    startAnimations[1]->setTargetObject(animationStartPixmapItem);
 
335
    endAnimations[0]->setTargetObject(animationEndPixmapItem);
 
336
    endAnimations[1]->setTargetObject(animationEndPixmapItem);
 
337
    endAnimations[2]->setTargetObject(animationEndVkbOverlayItem);
 
338
    endAnimations[3]->setTargetObject(animationEndVkbOverlayItem);
 
339
 
 
340
 
 
341
    rotationAnimationGroup.clear();
 
342
    rotationAnimationGroup.addAnimation(startAnimations[0]);
 
343
    rotationAnimationGroup.addAnimation(startAnimations[1]);
 
344
    rotationAnimationGroup.addAnimation(endAnimations[0]);
 
345
    rotationAnimationGroup.addAnimation(endAnimations[1]);
 
346
    rotationAnimationGroup.addAnimation(endAnimations[2]);
 
347
    rotationAnimationGroup.addAnimation(endAnimations[3]);
 
348
}
 
349
 
 
350
MImRotationAnimation::~MImRotationAnimation() {
 
351
    QGraphicsScene *myScene = scene();
 
352
    setScene(0);
 
353
    delete myScene;
 
354
 
 
355
    delete damageMonitor;
 
356
    damageMonitor = 0;
 
357
 
 
358
    rotationAnimationGroup.clear();
 
359
}
 
360
 
 
361
 
 
362
QPixmap
 
363
MImRotationAnimation::grabComposited()
 
364
{
 
365
    if (!remoteWindow || remoteWindow->windowPixmap().isNull()) {
 
366
        return QPixmap();
 
367
    }
 
368
 
 
369
    // Explicitly copying here since we want to paint the keyboard on top.
 
370
    // Qt is unable to render text into QPixmap, therefore we use QImage here.
 
371
    QImage grabImage(remoteWindow->windowPixmap().toImage());
 
372
 
 
373
    // Overlay keyboard, transparency not required
 
374
    QPainter painter(&grabImage);
 
375
    snapshotWidget->render(&painter,QPoint(0,0),QRect(0,0,width(),height()));
 
376
 
 
377
    return QPixmap::fromImage(grabImage);
 
378
}
 
379
 
 
380
QPixmap
 
381
MImRotationAnimation::grabVkbOnly()
 
382
{
 
383
    mServerLogic->setSuppressBackground(true);
 
384
    // We need to work with a QImage here, otherwise we lose the
 
385
    // transparency of the see-through part of the keyboard image.
 
386
    QImage grabImage(size(),QImage::Format_ARGB32);
 
387
    grabImage.fill(Qt::transparent);
 
388
 
 
389
    // Fill empty QImage with keyboard snapshot.
 
390
    QPainter painter(&grabImage);
 
391
    snapshotWidget->render(&painter,QPoint(0,0),QRect(0,0,width(),height()));
 
392
    // QPainter needs to be closed before returning
 
393
    // new QPixmap generated from QImage.
 
394
    painter.end();
 
395
 
 
396
    mServerLogic->setSuppressBackground(false);
 
397
 
 
398
    return QPixmap::fromImage(grabImage);
 
399
}
 
400
 
 
401
void
 
402
MImRotationAnimation::remoteWindowChanged(MImRemoteWindow* newRemoteWindow) {
 
403
    remoteWindow = newRemoteWindow;
 
404
    damageMonitor->remoteWindowChanged(newRemoteWindow);
 
405
 
 
406
    // Stop playing animations when underlying window is unmapped.
 
407
    if (!remoteWindow) {
 
408
        qDebug() << __PRETTY_FUNCTION__ << " - remote window gone, cancelling animation.";
 
409
        cancelAnimation();
 
410
    }
 
411
}
 
412
 
 
413
void
 
414
MImRotationAnimation::appOrientationAboutToChange(int toAngle) {
 
415
    qDebug() << __PRETTY_FUNCTION__ << " - toAngle: " << toAngle;
 
416
 
 
417
    if (!mServerLogic->passThruWindow()->isVisible()
 
418
        || toAngle == currentOrientationAngle
 
419
        || aboutToChangeReceived) {
 
420
        return;
 
421
    }
 
422
    startOrientationAngle = currentOrientationAngle;
 
423
 
 
424
    // Assuming that in self-composited case we don't need
 
425
    // extra redirection, we're already redirected.
 
426
    if (xOptions.selfComposited && remoteWindow) {
 
427
        remoteWindow->redirect();
 
428
    }
 
429
 
 
430
    // Capturing initial snapshot that's used for the beginning of the rotation.
 
431
    compositeWindowStart = grabComposited();
 
432
 
 
433
    // Do not do anything if no initial snapshot is available
 
434
    if (compositeWindowStart.isNull())
 
435
        return;
 
436
 
 
437
    // Clean up if we were still in the middle of a previous rotation animation.
 
438
    rotationAnimationGroup.stop();
 
439
    if (scene()) {
 
440
        scene()->clear();
 
441
    }
 
442
 
 
443
    setupScene();
 
444
    showInitial();
 
445
 
 
446
    damageMonitor->activate();
 
447
 
 
448
    // Unfortunately, we need to shield against appOrientationChangeFinishedEvents
 
449
    // that come in without a previous "AboutToChange" event.
 
450
    aboutToChangeReceived = true;
 
451
}
 
452
 
 
453
void
 
454
MImRotationAnimation::appOrientationChangeFinished(int toAngle) {
 
455
    qDebug() << __PRETTY_FUNCTION__ << " - toAngle: " << toAngle
 
456
            << " startOrientationAngle: " << startOrientationAngle;
 
457
 
 
458
    currentOrientationAngle = toAngle;
 
459
 
 
460
    if (!mServerLogic->passThruWindow()->isVisible()
 
461
        || toAngle == startOrientationAngle
 
462
        || !aboutToChangeReceived) {
 
463
        clearScene();
 
464
        aboutToChangeReceived = false;
 
465
        return;
 
466
    }
 
467
 
 
468
    setupAnimation(startOrientationAngle, toAngle);
 
469
 
 
470
    damageMonitor->waitForDamage();
 
471
}
 
472
 
 
473
 
 
474
void
 
475
MImRotationAnimation::startAnimation()
 
476
{
 
477
    qDebug() << __PRETTY_FUNCTION__;
 
478
 
 
479
    rotationAnimationGroup.start();
 
480
    aboutToChangeReceived = false;
 
481
}
 
482
 
 
483
void MImRotationAnimation::clearScene() {
 
484
    // When self-compositing is off, we don't need to maintain
 
485
    // the redirection.
 
486
    if (not xOptions.selfComposited && remoteWindow) {
 
487
        remoteWindow->unredirect();
 
488
    }
 
489
 
 
490
    hide();
 
491
 
 
492
    if (scene()) {
 
493
        scene()->clear();
 
494
    }
 
495
 
 
496
    compositeWindowStart = QPixmap();
 
497
}