WIP: Merge common code in LayoutUpdater whem making WordCandidate active/inactive
[maliit:maliit-plugins.git] / maliit-keyboard / lib / logic / layoutupdater.cpp
1 // -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; c-file-offsets: ((innamespace . 0)); -*-
2 /*
3  * This file is part of Maliit Plugins
4  *
5  * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). All rights reserved.
6  *
7  * Contact: Mohammad Anwari <Mohammad.Anwari@nokia.com>
8  *
9  * Redistribution and use in source and binary forms, with or without modification,
10  * are permitted provided that the following conditions are met:
11  *
12  * Redistributions of source code must retain the above copyright notice, this list
13  * of conditions and the following disclaimer.
14  * Redistributions in binary form must reproduce the above copyright notice, this list
15  * of conditions and the following disclaimer in the documentation and/or other materials
16  * provided with the distribution.
17  * Neither the name of Nokia Corporation nor the names of its contributors may be
18  * used to endorse or promote products derived from this software without specific
19  * prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
22  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
23  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
24  * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  *
31  */
32
33 #include "layoutupdater.h"
34 #include "style.h"
35
36 #include "models/area.h"
37 #include "models/keyboard.h"
38 #include "models/keydescription.h"
39 #include "models/wordribbon.h"
40 #include "models/wordcandidate.h"
41 #include "models/text.h"
42
43 #include "logic/keyareaconverter.h"
44 #include "logic/state-machines/shiftmachine.h"
45 #include "logic/state-machines/viewmachine.h"
46 #include "logic/state-machines/deadkeymachine.h"
47
48 namespace MaliitKeyboard {
49 namespace {
50
51 enum ActivationPolicy {
52     ActivateElement,
53     DeactivateElement
54 };
55
56 Key makeActive(const Key &key,
57                const Style *style)
58 {
59     Key k(key);
60
61     // FIXME: Use correct key style, but where to look it up?
62     k.rArea().setBackground(style->keyBackground(KeyDescription::NormalStyle, KeyDescription::PressedState));
63     k.rArea().setBackgroundBorders(style->keyBackgroundBorders());
64
65     return k;
66 }
67
68 void applyStyleToCandidate(WordCandidate *candidate,
69                            const Style *style,
70                            ActivationPolicy policy)
71 {
72     if (not candidate || not style) {
73         return;
74     }
75
76     Label &label(candidate->rLabel());
77     Font f(label.font());
78     f.setSize(17);
79
80     QByteArray color;
81     switch(policy) {
82     case ActivateElement:
83         color = QByteArray("#fff");
84         break;
85
86     case DeactivateElement:
87         color = QByteArray("#ddd");
88         break;
89     }
90
91     f.setColor(color);
92     label.setFont(f);
93 }
94
95 bool updateWordRibbon(const SharedLayout &layout,
96                       const WordCandidate &candidate,
97                       const Style *style,
98                       ActivationPolicy policy)
99 {
100     if (layout.isNull() || not style) {
101         return false;
102     }
103
104     WordRibbon ribbon(layout->wordRibbon());
105     QVector<WordCandidate> &candidates(ribbon.rCandidates());
106
107     for (int index = 0; index < candidates.count(); ++index) {
108         WordCandidate &current(candidates[index]);
109
110         if (current.label().text() == candidate.label().text()
111             && current.rect() == candidate.rect()) {
112             applyStyleToCandidate(&current, style, policy);
113             layout->setWordRibbon(ribbon);
114
115             return true;
116         }
117     }
118
119     return false;
120 }
121
122 Key magnifyKey(const Key &key,
123                const Style *style,
124                const QRectF &key_area_rect)
125 {
126     // FIXME: Remove test code
127     // TEST CODE STARTS
128     Font magnifier_font;
129     magnifier_font.setName(style->fontName());
130     magnifier_font.setSize(50);
131     magnifier_font.setColor(QByteArray("#ffffff"));
132
133     static const QMargins bg_margins(6, 6, 6, 6);
134     // TEST CODE ENDS
135
136     if (key.action() != Key::ActionInsert) {
137         return Key();
138     }
139
140     Key magnifier(key);
141     QRect magnifier_rect(key.rect().translated(0, -120).adjusted(-20, -20, 20, 20));
142     const QRect &mapped(magnifier_rect.translated(key_area_rect.topLeft().toPoint()));
143
144     const int delta_left(mapped.left() - (key_area_rect.left() + 10));
145     const int delta_right((key_area_rect.right() - 10) - mapped.right());
146
147     if (delta_left < 0) {
148         magnifier_rect.translate(qAbs<int>(delta_left), 0);
149     } else if (delta_right < 0) {
150         magnifier_rect.translate(delta_right, 0);
151     }
152
153     magnifier.setOrigin(magnifier_rect.topLeft());
154     magnifier.rArea().setBackground(style->keyBackground(KeyDescription::NormalStyle,
155                                                          KeyDescription::PressedState));
156     magnifier.rArea().setSize(magnifier_rect.size());
157     magnifier.rArea().setBackgroundBorders(bg_margins);
158     magnifier.rLabel().setFont(magnifier_font);
159
160     return magnifier;
161 }
162
163 } // anonymous namespace
164
165 class LayoutUpdaterPrivate
166 {
167 public:
168     bool initialized;
169     SharedLayout layout;
170     KeyboardLoader loader;
171     ShiftMachine shift_machine;
172     ViewMachine view_machine;
173     DeadkeyMachine deadkey_machine;
174     QPoint anchor;
175     Style style;
176     Style extended_keys_style;
177
178     explicit LayoutUpdaterPrivate()
179         : initialized(false)
180         , layout()
181         , loader()
182         , shift_machine()
183         , view_machine()
184         , deadkey_machine()
185         , anchor()
186         , style()
187         , extended_keys_style()
188     {
189         style.setProfile("nokia-n9");
190         extended_keys_style.setProfile("nokia-n9-extended-keys");
191     }
192
193     bool inShiftedState() const
194     {
195         return (shift_machine.inState(ShiftMachine::shift_state) or
196                 shift_machine.inState(ShiftMachine::caps_lock_state) or
197                 shift_machine.inState(ShiftMachine::latched_shift_state));
198     }
199
200     bool areSymbolsShown() const
201     {
202         return (view_machine.inState(ViewMachine::symbols0_state) or
203                 view_machine.inState(ViewMachine::symbols1_state));
204     }
205
206     bool inDeadkeyState() const
207     {
208         return (deadkey_machine.inState(DeadkeyMachine::deadkey_state) or
209                 deadkey_machine.inState(DeadkeyMachine::latched_deadkey_state));
210     }
211
212     const Style * activeStyle() const
213     {
214         return (layout->activePanel() == Layout::ExtendedPanel
215                 ? &extended_keys_style : &style);
216     }
217 };
218
219 LayoutUpdater::LayoutUpdater(QObject *parent)
220     : QObject(parent)
221     , d_ptr(new LayoutUpdaterPrivate)
222 {
223     connect(&d_ptr->loader, SIGNAL(keyboardsChanged()),
224             this,           SLOT(onKeyboardsChanged()),
225             Qt::UniqueConnection);
226 }
227
228 LayoutUpdater::~LayoutUpdater()
229 {}
230
231 void LayoutUpdater::init()
232 {
233     Q_D(LayoutUpdater);
234
235     d->shift_machine.setup(this);
236     d->view_machine.setup(this);
237     d->deadkey_machine.setup(this);
238 }
239
240 QStringList LayoutUpdater::keyboardIds() const
241 {
242     Q_D(const LayoutUpdater);
243     return d->loader.ids();
244 }
245
246 QString LayoutUpdater::activeKeyboardId() const
247 {
248     Q_D(const LayoutUpdater);
249     return d->loader.activeId();
250 }
251
252 void LayoutUpdater::setActiveKeyboardId(const QString &id)
253 {
254     Q_D(LayoutUpdater);
255     d->loader.setActiveId(id);
256 }
257
258 QString LayoutUpdater::keyboardTitle(const QString &id) const
259 {
260     Q_D(const LayoutUpdater);
261     return d->loader.title(id);
262 }
263
264 void LayoutUpdater::setLayout(const SharedLayout &layout)
265 {
266     Q_D(LayoutUpdater);
267     d->layout = layout;
268
269     if (not d->initialized) {
270         init();
271         d->initialized = true;
272     }
273 }
274
275 void LayoutUpdater::setOrientation(Layout::Orientation orientation)
276 {
277     Q_D(LayoutUpdater);
278
279     if (d->layout && d->layout->orientation() != orientation) {
280         d->layout->setOrientation(orientation);
281
282         const KeyAreaConverter converter(&d->style, &d->loader, d->anchor);
283         d->layout->setCenterPanel(d->inShiftedState() ? converter.shiftedKeyArea(orientation)
284                                                       : converter.keyArea(orientation));
285
286         clearActiveKeysAndMagnifier();
287         Q_EMIT layoutChanged(d->layout);
288     }
289 }
290
291 void LayoutUpdater::onKeyPressed(const Key &key,
292                                  const SharedLayout &layout)
293 {
294     Q_D(LayoutUpdater);
295
296     if (d->layout != layout) {
297         return;
298     }
299
300     layout->appendActiveKey(makeActive(key, d->activeStyle()));
301
302     if (d->layout->activePanel() == Layout::CenterPanel) {
303         layout->setMagnifierKey(magnifyKey(key, d->activeStyle(),
304                                            d->layout->centerPanelGeometry()));
305     }
306
307     switch (key.action()) {
308     case Key::ActionShift:
309         Q_EMIT shiftPressed();
310         break;
311
312     case Key::ActionDead:
313         d->deadkey_machine.setAccentKey(key);
314         Q_EMIT deadkeyPressed();
315         break;
316
317     default:
318         break;
319     }
320
321     Q_EMIT keysChanged(layout);
322 }
323
324 void LayoutUpdater::onKeyLongPressed(const Key &key,
325                                      const SharedLayout &layout)
326 {
327     Q_UNUSED(key);
328     Q_D(LayoutUpdater);
329
330     if (d->layout != layout || d->layout.isNull()) {
331         return;
332     }
333
334     clearActiveKeysAndMagnifier();
335
336     const Layout::Orientation orientation(d->layout->orientation());
337     const KeyAreaConverter converter(&d->extended_keys_style, &d->loader, d->anchor);
338     KeyArea ext_ka(converter.extendedKeyArea(orientation, key));
339
340     if (not ext_ka.hasKeys()) {
341         return;
342     }
343
344     const QSize &ext_panel_size(ext_ka.area().size());
345     const QSize &center_panel_size(d->layout->centerPanel().area().size());
346     const QPointF &key_center(key.rect().center());
347     const qreal safety_margin(d->extended_keys_style.safetyMargin(orientation));
348
349     QPoint offset(qMax<int>(safety_margin, key_center.x() - ext_panel_size.width() / 2),
350                   key_center.y() - d->extended_keys_style.verticalOffset(orientation));
351
352     if (offset.x() + ext_panel_size.width() > center_panel_size.width()) {
353         offset.rx() = center_panel_size.width() - ext_panel_size.width() - safety_margin;
354     }
355
356     d->layout->setExtendedPanelOffset(offset);
357     d->layout->setExtendedPanel(ext_ka);
358     d->layout->setActivePanel(Layout::ExtendedPanel);
359     Q_EMIT layoutChanged(d->layout);
360 }
361
362 void LayoutUpdater::onKeyReleased(const Key &key,
363                                   const SharedLayout &layout)
364 {
365     Q_D(const LayoutUpdater);
366
367     if (d->layout != layout) {
368         return;
369     }
370
371     layout->removeActiveKey(key);
372     layout->clearMagnifierKey();
373
374     if (d->layout->activePanel() == Layout::ExtendedPanel) {
375         d->layout->clearActiveKeys();
376         d->layout->setExtendedPanel(KeyArea());
377         d->layout->setActivePanel(Layout::CenterPanel);
378         Q_EMIT layoutChanged(d->layout);
379     }
380
381     switch (key.action()) {
382     case Key::ActionShift:
383         Q_EMIT shiftReleased();
384         break;
385
386     case Key::ActionInsert:
387         if (d->shift_machine.inState(ShiftMachine::latched_shift_state)) {
388             Q_EMIT shiftCancelled();
389         }
390
391         if (d->deadkey_machine.inState(DeadkeyMachine::latched_deadkey_state)) {
392             Q_EMIT deadkeyCancelled();
393         }
394
395         break;
396
397     case Key::ActionSym:
398         Q_EMIT symKeyReleased();
399         break;
400
401     case Key::ActionSwitch:
402         Q_EMIT symSwitcherReleased();
403         break;
404
405     case Key::ActionDead:
406         Q_EMIT deadkeyReleased();
407         break;
408
409     default:
410         break;
411     }
412
413     Q_EMIT keysChanged(layout);
414 }
415
416 void LayoutUpdater::onKeyEntered(const Key &key,
417                                  const SharedLayout &layout)
418 {
419     Q_D(const LayoutUpdater);
420
421     if (d->layout != layout) {
422         return;
423     }
424
425     layout->appendActiveKey(makeActive(key, d->activeStyle()));
426
427     if (d->layout->activePanel() == Layout::CenterPanel) {
428         layout->setMagnifierKey(magnifyKey(key, d->activeStyle(), d->layout->centerPanelGeometry()));
429     }
430
431     Q_EMIT keysChanged(layout);
432 }
433
434 void LayoutUpdater::onKeyExited(const Key &key, const SharedLayout &layout)
435 {
436     Q_D(const LayoutUpdater);
437
438     if (d->layout != layout) {
439         return;
440     }
441
442     layout->removeActiveKey(key);
443     layout->clearMagnifierKey(); // FIXME: This is in a race with onKeyEntered.
444     Q_EMIT keysChanged(layout);
445 }
446
447 void LayoutUpdater::clearActiveKeysAndMagnifier()
448 {
449     Q_D(const LayoutUpdater);
450
451     if (d->layout.isNull()) {
452         qCritical() << __PRETTY_FUNCTION__
453                     << "No layout specified.";
454         return;
455     }
456
457     d->layout->clearActiveKeys();
458     d->layout->clearMagnifierKey();
459 }
460
461 void LayoutUpdater::onCandidatesUpdated(const QStringList &candidates)
462 {
463     Q_D(LayoutUpdater);
464
465     if (d->layout.isNull()) {
466         qWarning() << __PRETTY_FUNCTION__
467                    << "No layout specified.";
468         return;
469     }
470
471     // Copy WordRibbon instance in order to preserve geometry and styling:
472     WordRibbon ribbon(d->layout->wordRibbon());
473     ribbon.clearCandidates();
474
475     const Style * const style(d->activeStyle());
476     const Layout::Orientation orientation(d->layout->orientation());
477     const int candidate_width(style->keyAreaWidth(orientation) / (orientation == Layout::Landscape ? 6 : 4));
478
479     for (int index = 0; index < candidates.count(); ++index) {
480         WordCandidate word_candidate;
481         word_candidate.rLabel().setText(candidates.at(index));
482         word_candidate.rArea().setSize(QSize(candidate_width, 56));
483         word_candidate.setOrigin(QPoint(index * candidate_width, 0));
484         applyStyleToCandidate(&word_candidate, d->activeStyle(), DeactivateElement);
485         ribbon.appendCandidate(word_candidate);
486     }
487
488     d->layout->setWordRibbon(ribbon);
489     Q_EMIT wordCandidatesChanged(d->layout);
490 }
491
492 void LayoutUpdater::onWordCandidatePressed(const WordCandidate &candidate,
493                                            const SharedLayout &layout)
494 {
495     Q_D(LayoutUpdater);
496
497     if (layout == d->layout
498         && updateWordRibbon(d->layout, candidate, d->activeStyle(), ActivateElement)) {
499         Q_EMIT wordCandidatesChanged(d->layout);
500     }
501 }
502
503 void LayoutUpdater::onWordCandidateReleased(const WordCandidate &candidate,
504                                             const SharedLayout &layout)
505 {
506     Q_D(LayoutUpdater);
507
508     if (layout == d->layout
509         && updateWordRibbon(d->layout, candidate, d->activeStyle(), DeactivateElement)) {
510         Q_EMIT wordCandidatesChanged(d->layout);
511         Q_EMIT wordCandidateSelected(candidate.label().text());
512     }
513 }
514
515 void LayoutUpdater::syncLayoutToView()
516 {
517     Q_D(const LayoutUpdater);
518
519     if (not d->layout) {
520         return;
521     }
522
523     // Symbols do not care about shift state.
524     if (d->areSymbolsShown()) {
525         return;
526     }
527
528     if (d->inDeadkeyState()) {
529         switchToAccentedView();
530     } else {
531         switchToMainView();
532     }
533 }
534
535 void LayoutUpdater::onKeyboardsChanged()
536 {
537     Q_D(LayoutUpdater);
538
539     // Resetting state machines should reset layout also.
540     // FIXME: Most probably reloading will happen three
541     // times, which is not what we want.
542     d->shift_machine.restart();
543     d->deadkey_machine.restart();
544     d->view_machine.restart();
545 }
546
547 void LayoutUpdater::switchToMainView()
548 {
549     Q_D(LayoutUpdater);
550
551     if (d->layout.isNull()) {
552         return;
553     }
554
555     d->layout->clearActiveKeys();
556     d->layout->clearMagnifierKey();
557
558     const KeyAreaConverter converter(&d->style, &d->loader, d->anchor);
559     const Layout::Orientation orientation(d->layout->orientation());
560     d->layout->setCenterPanel(d->inShiftedState() ? converter.shiftedKeyArea(orientation)
561                                                   : converter.keyArea(orientation));
562
563     Q_EMIT layoutChanged(d->layout);
564 }
565
566 void LayoutUpdater::switchToPrimarySymView()
567 {
568     Q_D(LayoutUpdater);
569
570     if (d->layout.isNull()) {
571         return;
572     }
573
574     const KeyAreaConverter converter(&d->style, &d->loader, d->anchor);
575     const Layout::Orientation orientation(d->layout->orientation());
576     d->layout->setCenterPanel(converter.symbolsKeyArea(orientation, 0));
577
578     // Reset shift state machine, also see switchToMainView.
579     d->shift_machine.restart();
580
581     //d->shift_machine->start();
582     Q_EMIT layoutChanged(d->layout);
583 }
584
585 void LayoutUpdater::switchToSecondarySymView()
586 {
587     Q_D(LayoutUpdater);
588
589     if (d->layout.isNull()) {
590         return;
591     }
592
593     const KeyAreaConverter converter(&d->style, &d->loader, d->anchor);
594     const Layout::Orientation orientation(d->layout->orientation());
595     d->layout->setCenterPanel(converter.symbolsKeyArea(orientation, 1));
596
597     Q_EMIT layoutChanged(d->layout);
598 }
599
600 void LayoutUpdater::switchToAccentedView()
601 {
602     Q_D(LayoutUpdater);
603
604     if (d->layout.isNull()) {
605         return;
606     }
607
608     const KeyAreaConverter converter(&d->style, &d->loader, d->anchor);
609     const Layout::Orientation orientation(d->layout->orientation());
610     const Key accent(d->deadkey_machine.accentKey());
611     d->layout->setCenterPanel(d->inShiftedState() ? converter.shiftedDeadKeyArea(orientation, accent)
612                                                   : converter.deadKeyArea(orientation, accent));
613
614     Q_EMIT layoutChanged(d->layout);
615 }
616
617 } // namespace MaliitKeyboard