~michael-sheldon/ubuntu-keyboard/layout-improvements

« back to all changes in this revision

Viewing changes to src/view/glass.cpp

  • Committer: Thomas Moenicke
  • Date: 2013-07-19 12:05:07 UTC
  • Revision ID: thomas.moenicke@canonical.com-20130719120507-lzw5oq50xm567x0j
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * This file is part of Maliit Plugins
 
3
 *
 
4
 * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). All rights reserved.
 
5
 *
 
6
 * Contact: Mohammad Anwari <Mohammad.Anwari@nokia.com>
 
7
 *
 
8
 * Redistribution and use in source and binary forms, with or without modification,
 
9
 * are permitted provided that the following conditions are met:
 
10
 *
 
11
 * Redistributions of source code must retain the above copyright notice, this list
 
12
 * of conditions and the following disclaimer.
 
13
 * Redistributions in binary form must reproduce the above copyright notice, this list
 
14
 * of conditions and the following disclaimer in the documentation and/or other materials
 
15
 * provided with the distribution.
 
16
 * Neither the name of Nokia Corporation nor the names of its contributors may be
 
17
 * used to endorse or promote products derived from this software without specific
 
18
 * prior written permission.
 
19
 *
 
20
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 
21
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 
22
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 
23
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 
24
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 
25
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 
26
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 
27
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 
28
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
29
 *
 
30
 */
 
31
 
 
32
#include "glass.h"
 
33
#include "logic/hitlogic.h"
 
34
#include "models/keyarea.h"
 
35
#include "models/wordribbon.h"
 
36
 
 
37
#include <QGraphicsView>
 
38
#include <QWidget>
 
39
 
 
40
namespace MaliitKeyboard {
 
41
 
 
42
namespace {
 
43
void removeActiveKey(QVector<Key> *active_keys,
 
44
                     const Key &key)
 
45
{
 
46
    if (not active_keys) {
 
47
        return;
 
48
    }
 
49
 
 
50
    for (int index = 0; index < active_keys->count(); ++index) {
 
51
        if (active_keys->at(index) == key) {
 
52
            active_keys->remove(index);
 
53
            break;
 
54
        }
 
55
    }
 
56
}
 
57
 
 
58
}
 
59
 
 
60
class GlassPrivate
 
61
{
 
62
public:
 
63
    QWidget *window;
 
64
    QWidget *extendedWindow;
 
65
    QSharedPointer<Maliit::Plugins::AbstractGraphicsViewSurface> surface;
 
66
    QSharedPointer<Maliit::Plugins::AbstractGraphicsViewSurface> extendedSurface;
 
67
    QVector<Logic::LayoutHelper *> layouts;
 
68
    QVector<Key> active_keys;
 
69
    WordCandidate active_candidate;
 
70
    QPoint last_pos;
 
71
    QPoint press_pos;
 
72
    QElapsedTimer gesture_timer;
 
73
    bool gesture_triggered;
 
74
    QTimer long_press_timer;
 
75
    Logic::LayoutHelper *long_press_layout;
 
76
 
 
77
    explicit GlassPrivate()
 
78
        : window(0)
 
79
        , extendedWindow(0)
 
80
        , surface()
 
81
        , extendedSurface()
 
82
        , layouts()
 
83
        , active_keys()
 
84
        , active_candidate()
 
85
        , last_pos()
 
86
        , press_pos()
 
87
        , gesture_timer()
 
88
        , gesture_triggered(false)
 
89
        , long_press_timer()
 
90
        , long_press_layout()
 
91
    {
 
92
        long_press_timer.setInterval(300);
 
93
        long_press_timer.setSingleShot(true);
 
94
    }
 
95
};
 
96
 
 
97
Glass::Glass(QObject *parent)
 
98
    : QObject(parent)
 
99
    , d_ptr(new GlassPrivate)
 
100
{
 
101
    connect(&d_ptr->long_press_timer, SIGNAL(timeout()),
 
102
            this,                     SLOT(onLongPressTriggered()),
 
103
            Qt::UniqueConnection);
 
104
}
 
105
 
 
106
Glass::~Glass()
 
107
{}
 
108
 
 
109
void Glass::setSurface(const QSharedPointer<Maliit::Plugins::AbstractGraphicsViewSurface> &surface)
 
110
{
 
111
    Q_D(Glass);
 
112
 
 
113
    QWidget *window = surface ? surface->view()->viewport() : 0;
 
114
 
 
115
    if (not window) {
 
116
        qCritical() << __PRETTY_FUNCTION__
 
117
                    << "No window given";
 
118
        return;
 
119
    }
 
120
 
 
121
    d->surface = surface;
 
122
    d->window = window;
 
123
    clearLayouts();
 
124
 
 
125
    d->window->installEventFilter(this);
 
126
}
 
127
 
 
128
void Glass::setExtendedSurface(const QSharedPointer<Maliit::Plugins::AbstractGraphicsViewSurface> &surface)
 
129
{
 
130
    Q_D(Glass);
 
131
 
 
132
    QWidget *window = surface ? surface->view()->viewport() : 0;
 
133
 
 
134
    if (not window) {
 
135
        qCritical() << __PRETTY_FUNCTION__
 
136
                    << "No window given";
 
137
        return;
 
138
    }
 
139
 
 
140
    d->extendedSurface = surface;
 
141
    d->extendedWindow = window;
 
142
 
 
143
    window->installEventFilter(this);
 
144
}
 
145
 
 
146
void Glass::addLayout(Logic::LayoutHelper *layout)
 
147
{
 
148
    Q_D(Glass);
 
149
    d->layouts.append(layout);
 
150
}
 
151
 
 
152
void Glass::clearLayouts()
 
153
{
 
154
    Q_D(Glass);
 
155
    d->layouts.clear();
 
156
}
 
157
 
 
158
bool Glass::eventFilter(QObject *obj,
 
159
                        QEvent *ev)
 
160
{
 
161
    Q_D(Glass);
 
162
    static bool measure_fps(QCoreApplication::arguments().contains("-measure-fps"));
 
163
 
 
164
    if (not obj || not ev) {
 
165
        return false;
 
166
    }
 
167
 
 
168
    const QSharedPointer<Maliit::Plugins::AbstractGraphicsViewSurface> &eventSurface(obj == d->extendedWindow ? d->extendedSurface : d->surface);
 
169
 
 
170
    switch(ev->type()) {
 
171
    case QEvent::Paint: {
 
172
        if (measure_fps) {
 
173
            static int count = 0;
 
174
            static QElapsedTimer fps_timer;
 
175
 
 
176
            if (0 == count % 120) {
 
177
                qDebug() << "FPS:" << count / ((0.01 + fps_timer.elapsed()) / 1000) << count;
 
178
                fps_timer.restart();
 
179
                count = 0;
 
180
            }
 
181
 
 
182
            d->window->update();
 
183
            ++count;
 
184
        }
 
185
    } break;
 
186
 
 
187
    case QKeyEvent::MouseButtonPress:
 
188
        d->gesture_timer.restart();
 
189
        d->gesture_triggered = false;
 
190
 
 
191
        return handlePressReleaseEvent(ev, eventSurface);
 
192
 
 
193
    case QKeyEvent::MouseButtonRelease:
 
194
        d->long_press_timer.stop();
 
195
 
 
196
        if (d->gesture_triggered) {
 
197
            return false;
 
198
        }
 
199
 
 
200
        return handlePressReleaseEvent(ev, eventSurface);
 
201
 
 
202
    case QKeyEvent::MouseMove: {
 
203
        if (d->gesture_triggered) {
 
204
            return false;
 
205
        }
 
206
 
 
207
        QMouseEvent *qme = static_cast<QMouseEvent *>(ev);
 
208
        ev->accept();
 
209
 
 
210
        Q_FOREACH (Logic::LayoutHelper *layout, d->layouts) {
 
211
            const QSharedPointer<Maliit::Plugins::AbstractGraphicsViewSurface> targetSurface(layout->activePanel() == Logic::LayoutHelper::ExtendedPanel ? d->extendedSurface : d->surface);
 
212
 
 
213
            const QPoint &pos(targetSurface->translateEventPosition(qme->pos(), eventSurface));
 
214
            const QPoint &last_pos(targetSurface->translateEventPosition(d->last_pos, eventSurface));
 
215
            d->last_pos = qme->pos();
 
216
 
 
217
            const QPoint &press_pos(targetSurface->translateEventPosition(d->press_pos, eventSurface));
 
218
 
 
219
            const QRect &rect(layout->activeKeyAreaGeometry());
 
220
 
 
221
            if (d->gesture_timer.elapsed() < 250) {
 
222
                if (pos.y() > (press_pos.y() - rect.height() * 0.33)
 
223
                    && pos.y() < (press_pos.y() + rect.height() * 0.33)) {
 
224
                    if (pos.x() < (press_pos.x() - rect.width() * 0.33)) {
 
225
                        d->gesture_triggered = true;
 
226
                        Q_EMIT switchRight(layout);
 
227
                    } else if (pos.x() > (press_pos.x() + rect.width() * 0.33)) {
 
228
                        d->gesture_triggered = true;
 
229
                        Q_EMIT switchLeft(layout);
 
230
                    }
 
231
                } else if (pos.x() > (press_pos.x() - rect.width() * 0.33)
 
232
                           && pos.x() < (press_pos.x() + rect.width() * 0.33)) {
 
233
                    if (pos.y() > (press_pos.y() + rect.height() * 0.50)) {
 
234
                        d->gesture_triggered = true;
 
235
                        Q_EMIT keyboardClosed();
 
236
                    }
 
237
                }
 
238
            }
 
239
 
 
240
            if (d->gesture_triggered) {
 
241
                Q_FOREACH (const Key &k, d->active_keys) {
 
242
                    Q_EMIT keyExited(k, layout);
 
243
                }
 
244
 
 
245
                d->active_keys.clear();
 
246
 
 
247
                return true;
 
248
            }
 
249
 
 
250
            const Key &last_key(Logic::keyHit(d->active_keys, rect, last_pos));
 
251
 
 
252
 
 
253
            const Key &key(Logic::keyHit(layout->activeKeyArea().keys(),
 
254
                                         layout->activeKeyAreaGeometry(),
 
255
                                         pos));
 
256
 
 
257
            if (last_key != key) {
 
258
                if (last_key.valid()) {
 
259
                    removeActiveKey(&d->active_keys, last_key);
 
260
                    d->long_press_timer.stop();
 
261
                    Q_EMIT keyExited(last_key, layout);
 
262
                }
 
263
 
 
264
                if (key.valid()) {
 
265
                    d->active_keys.append(key);
 
266
 
 
267
                    if (key.hasExtendedKeys() || key.action() == Key::ActionSpace) {
 
268
                        d->long_press_timer.start();
 
269
                        d->long_press_layout = layout;
 
270
                    }
 
271
 
 
272
                    Q_EMIT keyEntered(key, layout);
 
273
                }
 
274
                // TODO: CHECK
 
275
                return true;
 
276
            }
 
277
        }
 
278
    } break;
 
279
 
 
280
    default:
 
281
        break;
 
282
    }
 
283
 
 
284
    return false;
 
285
}
 
286
 
 
287
void Glass::onLongPressTriggered()
 
288
{
 
289
    Q_D(Glass);
 
290
 
 
291
    if (d->gesture_triggered
 
292
        || d->active_keys.isEmpty()
 
293
        || not d->long_press_layout
 
294
        || d->long_press_layout->activePanel() == Logic::LayoutHelper::ExtendedPanel) {
 
295
        return;
 
296
    }
 
297
 
 
298
    Q_FOREACH (const Key &k, d->active_keys) {
 
299
        Q_EMIT keyExited(k, d->long_press_layout); // Not necessarily correct layout for the key ...
 
300
    }
 
301
 
 
302
    Q_EMIT keyLongPressed(d->active_keys.last(), d->long_press_layout);
 
303
    d->active_keys.clear();
 
304
}
 
305
 
 
306
bool Glass::handlePressReleaseEvent(QEvent *ev,
 
307
                                    const QSharedPointer<Maliit::Plugins::AbstractGraphicsViewSurface> &eventSurface)
 
308
{
 
309
    if (not ev) {
 
310
        return false;
 
311
    }
 
312
 
 
313
    Q_D(Glass);
 
314
 
 
315
    bool consumed = false;
 
316
    QMouseEvent *qme = static_cast<QMouseEvent *>(ev);
 
317
    d->last_pos = qme->pos();
 
318
    d->press_pos = qme->pos(); // FIXME: dont update on mouse release, clear instead.
 
319
    ev->accept();
 
320
 
 
321
    Q_FOREACH (Logic::LayoutHelper *layout, d->layouts) {
 
322
        const QSharedPointer<Maliit::Plugins::AbstractGraphicsViewSurface> targetSurface(layout->activePanel() == Logic::LayoutHelper::ExtendedPanel ? d->extendedSurface : d->surface);
 
323
 
 
324
        const QPoint &pos(targetSurface->translateEventPosition(qme->pos(), eventSurface));
 
325
 
 
326
        switch (qme->type()) {
 
327
        case QKeyEvent::MouseButtonPress: {
 
328
            const Key &key(Logic::keyHit(layout->activeKeyArea().keys(),
 
329
                                         layout->activeKeyAreaGeometry(),
 
330
                                         pos, d->active_keys));
 
331
 
 
332
            if (key.valid()) {
 
333
                d->active_keys.append(key);
 
334
                Q_EMIT keyPressed(key, layout);
 
335
                Q_EMIT keyAreaPressed(layout->activePanel(), layout);
 
336
 
 
337
                if (key.hasExtendedKeys() || key.action() == Key::ActionSpace) {
 
338
                    d->long_press_timer.start();
 
339
                    d->long_press_layout = layout;
 
340
                }
 
341
 
 
342
                consumed = true;
 
343
            } else {
 
344
                const WordCandidate &candidate(Logic::wordCandidateHit(layout->wordRibbon()->candidates(),
 
345
                                                                       layout->wordRibbon()->rect(),
 
346
                                                                       pos));
 
347
 
 
348
                if (candidate.valid()) {
 
349
                    d->active_candidate = candidate;
 
350
                    Q_EMIT wordCandidatePressed(candidate, layout);
 
351
                    consumed = true;
 
352
                } else {
 
353
                    // Hit empty area, we use keyAreaReleased to cancel any user action.
 
354
                    // TODO: Better introduce cancelled signal?
 
355
                    Q_EMIT keyAreaReleased(Logic::LayoutHelper::NumPanels, layout);
 
356
                }
 
357
            }
 
358
        } break;
 
359
 
 
360
        case QKeyEvent::MouseButtonRelease: {
 
361
            const Key &key(Logic::keyHit(layout->activeKeyArea().keys(),
 
362
                                         layout->activeKeyAreaGeometry(),
 
363
                                         pos, d->active_keys, Logic::AcceptIfInFilter));
 
364
 
 
365
            if (key.valid()) {
 
366
                removeActiveKey(&d->active_keys, key);
 
367
                Q_EMIT keyReleased(key, layout);
 
368
                Q_EMIT keyAreaReleased(layout->activePanel(), layout);
 
369
                consumed = true;
 
370
            } else {
 
371
                const WordCandidate &candidate(Logic::wordCandidateHit(layout->wordRibbon()->candidates(),
 
372
                                                                       layout->wordRibbon()->rect(),
 
373
                                                                       pos));
 
374
 
 
375
                if (candidate.valid() && candidate == d->active_candidate) {
 
376
                    d->active_candidate = WordCandidate();
 
377
                    Q_EMIT wordCandidateReleased(candidate, layout);
 
378
                    consumed = true;
 
379
                }
 
380
            }
 
381
 
 
382
        } break;
 
383
 
 
384
        default:
 
385
            break;
 
386
        }
 
387
 
 
388
        Logic::LayoutHelper::Panel panel = Logic::LayoutHelper::NumPanels;
 
389
 
 
390
        if (layout->centerPanel().rect().contains(pos))
 
391
            panel = Logic::LayoutHelper::CenterPanel;
 
392
        else if (QRect(d->extendedSurface->relativePosition(), d->extendedSurface->size()).contains(pos))
 
393
            panel = Logic::LayoutHelper::ExtendedPanel;
 
394
        else if (layout->leftPanel().rect().contains(pos))
 
395
            panel = Logic::LayoutHelper::LeftPanel;
 
396
        else if (layout->rightPanel().rect().contains(pos))
 
397
            panel = Logic::LayoutHelper::RightPanel;
 
398
 
 
399
        if (panel != Logic::LayoutHelper::NumPanels) {
 
400
            if (qme->type() == QKeyEvent::MouseButtonPress) {
 
401
                Q_EMIT keyAreaPressed(panel, layout);
 
402
            } else if (qme->type() == QKeyEvent::MouseButtonRelease) {
 
403
                Q_EMIT keyAreaReleased(panel, layout);
 
404
            }
 
405
 
 
406
            return true;
 
407
        }
 
408
    }
 
409
 
 
410
    return consumed;
 
411
}
 
412
 
 
413
} // namespace MaliitKeyboard