1
// -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; c-file-offsets: ((innamespace . 0)); -*-
3
* This file is part of Maliit Plugins
5
* Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). All rights reserved.
7
* Contact: Mohammad Anwari <Mohammad.Anwari@nokia.com>
9
* Redistribution and use in source and binary forms, with or without modification,
10
* are permitted provided that the following conditions are met:
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.
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.
37
#include "parser/layoutparser.h"
38
#include "coreutils.h"
40
#include "keyboardloader.h"
44
using namespace MaliitKeyboard;
46
// For getting languages directory we use a function instead of global constant
47
// because global constants are initialized before main runs. In this case that
48
// would mean that CoreUtils::pluginDataDirectory is ran before any code in main
49
// and it sets its static variable once. We want to be able to set the
50
// environment variable altering behaviour of pluginDataDirectory for testing
52
QString getLanguagesDir()
54
static QString languages_dir;
56
if (languages_dir.isEmpty()) {
57
// From http://doc.qt.nokia.com/4.7/qdir.html#separator: If you always
58
// use "/", Qt will translate your paths to conform to the underlying
60
languages_dir = CoreUtils::pluginDataDirectory() + "/languages";
66
typedef const QStringList (LayoutParser::*ParserFunc)() const;
68
TagKeyboardPtr getTagKeyboard(const QString &id)
71
return TagKeyboardPtr();
74
const QString path(getLanguagesDir() + "/" + id + ".xml");
78
file.open(QIODevice::ReadOnly);
80
LayoutParser parser(&file);
81
const bool result(parser.parse());
85
return parser.keyboard();
87
qWarning() << __PRETTY_FUNCTION__ << "Could not parse file:" << path << ", error:" << parser.errorString();
90
qWarning() << __PRETTY_FUNCTION__ << "File not found:" << path;
93
return TagKeyboardPtr();
96
QPair<Key, KeyDescription> keyAndDescFromTags(const TagKeyPtr &key,
97
const TagBindingPtr &binding,
101
KeyDescription skey_description;
103
skey.setExtendedKeysEnabled(key->extended());
104
skey.rLabel().setText(binding->label());
106
if (binding->dead()) {
107
// TODO: document it.
108
skey.setAction(Key::ActionDead);
110
skey.setAction(static_cast<Key::Action>(binding->action()));
113
skey.setCommandSequence(binding->sequence());
114
skey.setIcon(binding->icon().toUtf8());
115
skey.setStyle(static_cast<Key::Style>(key->style()));
117
skey_description.row = row;
118
skey_description.use_rtl_icon = key->rtl();
119
skey_description.left_spacer = false;
120
skey_description.right_spacer = false;
121
skey_description.width = static_cast<KeyDescription::Width>(key->width());
123
switch (skey.action()) {
124
case Key::ActionBackspace:
125
skey_description.icon = KeyDescription::BackspaceIcon;
127
case Key::ActionReturn:
128
skey_description.icon = KeyDescription::ReturnIcon;
130
case Key::ActionShift:
131
skey_description.icon = KeyDescription::ShiftIcon;
133
case Key::ActionClose:
134
skey_description.icon = KeyDescription::CloseIcon;
136
case Key::ActionLeftLayout:
137
skey_description.icon = KeyDescription::LeftLayoutIcon;
139
case Key::ActionRightLayout:
140
skey_description.icon = KeyDescription::RightLayoutIcon;
143
if (skey.icon().isEmpty()) {
144
skey_description.icon = KeyDescription::NoIcon;
146
skey_description.icon = KeyDescription::CustomIcon;
151
skey_description.font_group = KeyDescription::NormalFontGroup;
153
return qMakePair(skey, skey_description);
156
Keyboard getKeyboard(const TagKeyboardPtr &keyboard,
157
bool shifted = false,
159
const QString &dead_label = "")
162
const QChar dead_key((dead_label.size() == 1) ? dead_label[0] : QChar::Null);
165
TagLayoutPtrs layouts(keyboard->layouts());
167
if (not layouts.isEmpty()) {
168
const TagSectionPtrs sections(layouts.first()->sections());
169
// sections cannot be empty - parser does not allow that.
170
const TagSectionPtr section(sections[page % sections.size()]);
171
const TagRowPtrs rows(section->rows());
173
QString section_style(section->style());
176
Q_FOREACH (const TagRowPtr &row, rows) {
177
const TagRowElementPtrs elements(row->elements());
178
bool spacer_met(false);
180
Q_FOREACH (const TagRowElementPtr &element, elements) {
181
if (element->element_type() == TagRowElement::Key) {
182
const TagKeyPtr key(element.staticCast<TagKey>());
183
const TagBindingPtr binding(key->binding());
184
TagBindingPtr the_binding;
185
const TagModifiersPtrs all_modifiers(binding->modifiers());
189
if (not shifted or all_modifiers.isEmpty()) {
190
the_binding = binding;
192
Q_FOREACH (const TagModifiersPtr &modifiers, all_modifiers) {
193
if (modifiers->keys() == TagModifiers::Shift) {
194
the_binding = modifiers->binding();
197
if (not the_binding) {
198
the_binding = binding;
202
const int index(dead_key.isNull() ? -1 : the_binding->accents().indexOf(dead_key));
203
QPair<Key, KeyDescription> key_and_desc(keyAndDescFromTags(key, the_binding, row_num));
205
key_and_desc.first.rLabel().setText(index < 0 ? the_binding->label()
206
: the_binding->accented_labels().at(index));
207
key_and_desc.second.left_spacer = spacer_met;
208
key_and_desc.second.right_spacer = false;
210
skeyboard.keys.append(key_and_desc.first);
211
skeyboard.key_descriptions.append(key_and_desc.second);
214
if (not skeyboard.key_descriptions.isEmpty()) {
215
KeyDescription &previous_skey_description(skeyboard.key_descriptions.last());
217
if (previous_skey_description.row == row_num) {
218
previous_skey_description.right_spacer = true;
226
if (section_style.isEmpty()) {
227
section_style = "keys" + QString::number(key_count);
229
skeyboard.style_name = section_style;
235
QPair<TagKeyPtr, TagBindingPtr> getTagKeyAndBinding(const TagKeyboardPtr &keyboard,
236
const QString &label,
239
QPair<TagKeyPtr, TagBindingPtr> pair;
242
TagLayoutPtrs layouts(keyboard->layouts());
244
if (not layouts.isEmpty()) {
245
// sections cannot be empty - parser does not allow that.
246
TagRowPtrs rows(layouts.first()->sections().first()->rows());
248
Q_FOREACH (const TagRowPtr &row, rows) {
249
TagRowElementPtrs elements(row->elements());
251
Q_FOREACH (const TagRowElementPtr &element, elements) {
252
if (element->element_type() == TagRowElement::Key) {
253
TagKeyPtr key(element.staticCast<TagKey>());
254
TagBindingPtr the_binding;
255
TagBindingPtr binding(key->binding());
257
// Hotfix for suppressing long-press on space bringing
258
// up extended keys if another key has empty label, in
260
// FIXME: Make extended keyboard/keyarea part of key
261
// model instead, to avoid wrong lookups.
262
if (binding->action() == TagBinding::Space) {
265
if (binding->label() == label) {
266
the_binding = binding;
269
const TagModifiersPtrs all_modifiers(binding->modifiers());
271
Q_FOREACH (const TagModifiersPtr &modifiers, all_modifiers) {
272
const TagBindingPtr mod_binding(modifiers->binding());
274
if (mod_binding->label() == label) {
275
the_binding = mod_binding;
276
*shifted = (modifiers->keys() == TagModifiers::Shift);
284
pair.second = the_binding;
295
Keyboard getImportedKeyboard(const QString &id,
297
const QString &file_prefix,
298
const QString &default_file,
301
QString path(getLanguagesDir() + "/" + id + ".xml");
305
file.open(QIODevice::ReadOnly);
307
LayoutParser parser(&file);
308
const bool result(parser.parse());
312
const QStringList f_results((parser.*func)());
314
Q_FOREACH (const QString &f_result, f_results) {
315
const QFileInfo file_info(getLanguagesDir() + "/" + f_result);
317
if (file_info.exists() and file_info.isFile()) {
318
const TagKeyboardPtr keyboard(getTagKeyboard(file_info.baseName()));
319
return getKeyboard(keyboard, false, page);
323
// If we got there then it means that we got xml layout file that does not use
324
// new <import> syntax or just does not specify explicitly which file to import.
325
// In this case we have to search imports list for entry with filename beginning
327
const QStringList imports(parser.imports());
328
const QRegExp file_regexp("^(" + file_prefix + ".*).xml$");
330
Q_FOREACH (const QString &import, imports) {
331
if (file_regexp.exactMatch(import)) {
332
QFileInfo file_info(getLanguagesDir() + "/" + import);
334
if (file_info.exists() and file_info.isFile()) {
335
const TagKeyboardPtr keyboard(getTagKeyboard(file_regexp.cap(1)));
336
return getKeyboard(keyboard, false, page);
341
// If we got there then we try to just load a file with name in default_file.
342
QFileInfo file_info(getLanguagesDir() + "/" + default_file);
344
if (file_info.exists() and file_info.isFile()) {
345
const TagKeyboardPtr keyboard(getTagKeyboard(file_info.baseName()));
346
return getKeyboard(keyboard, false);
349
qWarning() << __PRETTY_FUNCTION__ << "Could not parse file:" << path << ", error:" << parser.errorString();
352
qWarning() << __PRETTY_FUNCTION__ << "File not found:" << path;
357
} // anonymous namespace
359
namespace MaliitKeyboard {
361
class KeyboardLoaderPrivate
368
KeyboardLoader::KeyboardLoader(QObject *parent)
370
, d_ptr(new KeyboardLoaderPrivate)
373
KeyboardLoader::~KeyboardLoader()
376
QStringList KeyboardLoader::ids() const
379
QDir dir(getLanguagesDir(),
381
QDir::Name | QDir::IgnoreCase,
382
QDir::Files | QDir::NoSymLinks | QDir::Readable);
385
QFileInfoList file_infos(dir.entryInfoList());
387
Q_FOREACH (const QFileInfo &file_info, file_infos) {
388
QFile file(file_info.filePath());
389
file.open(QIODevice::ReadOnly);
390
LayoutParser parser(&file);
392
if (parser.isLanguageFile()) {
393
ids.append(file_info.baseName());
400
QString KeyboardLoader::activeId() const
402
Q_D(const KeyboardLoader);
406
void KeyboardLoader::setActiveId(const QString &id)
410
if (d->active_id != id) {
413
// FIXME: Emit only after parsing new keyboard.
414
Q_EMIT keyboardsChanged();
418
QString KeyboardLoader::title(const QString &id) const
420
const TagKeyboardPtr keyboard(getTagKeyboard(id));
423
return keyboard->title();
429
Keyboard KeyboardLoader::keyboard() const
431
Q_D(const KeyboardLoader);
432
TagKeyboardPtr keyboard(getTagKeyboard(d->active_id));
434
return getKeyboard(keyboard);
437
Keyboard KeyboardLoader::nextKeyboard() const
439
Q_D(const KeyboardLoader);
441
const QStringList all_ids(ids());
443
if (all_ids.isEmpty()) {
447
int next_index(all_ids.indexOf(d->active_id) + 1);
449
if (next_index >= all_ids.size()) {
453
TagKeyboardPtr keyboard(getTagKeyboard(all_ids[next_index]));
455
return getKeyboard(keyboard);
458
Keyboard KeyboardLoader::previousKeyboard() const
460
Q_D(const KeyboardLoader);
462
const QStringList all_ids(ids());
464
if (all_ids.isEmpty()) {
468
int previous_index(all_ids.indexOf(d->active_id) - 1);
470
if (previous_index < 0) {
474
TagKeyboardPtr keyboard(getTagKeyboard(all_ids[previous_index]));
476
return getKeyboard(keyboard);
479
Keyboard KeyboardLoader::shiftedKeyboard() const
481
Q_D(const KeyboardLoader);
482
TagKeyboardPtr keyboard(getTagKeyboard(d->active_id));
484
return getKeyboard(keyboard, true);
487
Keyboard KeyboardLoader::symbolsKeyboard(int page) const
489
Q_D(const KeyboardLoader);
491
return getImportedKeyboard(d->active_id, &LayoutParser::symviews, "symbols", "symbols_en.xml", page);
494
Keyboard KeyboardLoader::deadKeyboard(const Key &dead) const
496
Q_D(const KeyboardLoader);
497
TagKeyboardPtr keyboard(getTagKeyboard(d->active_id));
499
return getKeyboard(keyboard, false, 0, dead.label().text());
502
Keyboard KeyboardLoader::shiftedDeadKeyboard(const Key &dead) const
504
Q_D(const KeyboardLoader);
505
TagKeyboardPtr keyboard(getTagKeyboard(d->active_id));
507
return getKeyboard(keyboard, true, 0, dead.label().text());
510
Keyboard KeyboardLoader::extendedKeyboard(const Key &key) const
512
// Hotfix for suppressing long-press on space bringing up extended keys if
513
// another key has empty label, in given layout.
514
// FIXME: Make extended keyboard/keyarea part of key model instead, to
515
// avoid wrong lookups.
516
if (key.action() == Key::ActionSpace) {
520
Q_D(const KeyboardLoader);
521
const TagKeyboardPtr keyboard(getTagKeyboard(d->active_id));
523
const QPair<TagKeyPtr, TagBindingPtr> pair(getTagKeyAndBinding(keyboard, key.label().text(), &shifted));
526
if (pair.first and pair.second) {
527
const TagExtendedPtr extended(pair.first->extended());
530
const TagRowPtrs rows(extended->rows());
533
Q_FOREACH (const TagRowPtr &row, rows) {
534
const TagRowElementPtrs elements(row->elements());
536
Q_FOREACH (const TagRowElementPtr &element, elements) {
537
switch (element->element_type()) {
538
case TagRowElement::Key: {
539
const TagKeyPtr key(element.staticCast<TagKey>());
540
const TagBindingPtr binding(key->binding());
541
TagBindingPtr the_binding;
544
const TagModifiersPtrs all_modifiers(binding->modifiers());
546
Q_FOREACH(const TagModifiersPtr &modifiers, all_modifiers) {
547
if (modifiers->keys() == TagModifiers::Shift) {
548
the_binding = modifiers->binding();
552
if (not the_binding) {
553
the_binding = binding;
556
QPair<Key, KeyDescription> key_and_desc(keyAndDescFromTags(key, the_binding, row_index));
558
skeyboard.keys.append(key_and_desc.first);
559
skeyboard.key_descriptions.append(key_and_desc.second);
562
case TagRowElement::Spacer:
568
// I don't like this prepending source key idea - it should be done
569
// in language layout file.
571
and not key.label().text().isEmpty()
572
and key.action() == Key::ActionInsert) {
573
Key first_key(skeyboard.keys.first());
574
KeyDescription first_desc(skeyboard.key_descriptions.first());
576
first_key.rLabel().setText(key.label().text());
577
first_key.setIcon(key.icon());
578
skeyboard.keys.prepend(first_key);
579
skeyboard.key_descriptions.prepend(first_desc);
586
Keyboard KeyboardLoader::numberKeyboard() const
588
Q_D(const KeyboardLoader);
590
return getImportedKeyboard(d->active_id, &LayoutParser::numbers, "number", "number.xml");
593
Keyboard KeyboardLoader::phoneNumberKeyboard() const
595
Q_D(const KeyboardLoader);
597
return getImportedKeyboard(d->active_id, &LayoutParser::phonenumbers, "phonenumber", "phonenumber.xml");
600
} // namespace MaliitKeyboard