From 24c10b0b8d7673f2d5e04766b7e3e0e65f771127 Mon Sep 17 00:00:00 2001 From: Gatis Paeglis Date: Fri, 1 Mar 2013 12:55:42 +0100 Subject: Introducing QComposeInputContext MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When switching from Xlib to xcb platform plugin it was agreed that XIM is deprecated. Users should be using QT_IM_MODULE to load input context plugin for a more advance input method framework support. The proposed solution is to parse the compose file directly from Qt. This approach removes the overhead of communication protocols used in Xlib and/or IBUS. TableGenerator class follows [1]. The compose file is searched for in the following order: 1) If the environment variable $XCOMPOSEFILE is set, its value is used as the name of the Compose file. 2) If the user’s home directory has a file named .XCompose, it is used as the Compose file. 3) The system provided compose file is used by mapping the locale to a compose file from the list in /usr/share/X11/locale/compose.dir. Regarding step 3 - TableGenerator searches in hard-coded locations for system-provided compose files. Here I have introcuded a new environment variable QTCOMPOSE which can be used to prepend an extra location to be searched. [1] http://www.x.org/archive/X11R7.7/doc/man/man5/Compose.5.xhtml Task-number: QTBUG-28183 Change-Id: I76dcfd454f3acc23db98192a3673c1ab2af4425f Reviewed-by: Samuel Rødal --- .../platforminputcontexts/compose/compose.json | 3 + .../platforminputcontexts/compose/compose.pro | 20 + .../compose/generator/qtablegenerator.cpp | 402 +++++++++++++++++++++ .../compose/generator/qtablegenerator.h | 128 +++++++ src/plugins/platforminputcontexts/compose/main.cpp | 70 ++++ .../compose/qcomposeplatforminputcontext.cpp | 268 ++++++++++++++ .../compose/qcomposeplatforminputcontext.h | 85 +++++ .../compose/xkbcommon_workaround.h | 105 ++++++ 8 files changed, 1081 insertions(+) create mode 100644 src/plugins/platforminputcontexts/compose/compose.json create mode 100644 src/plugins/platforminputcontexts/compose/compose.pro create mode 100644 src/plugins/platforminputcontexts/compose/generator/qtablegenerator.cpp create mode 100644 src/plugins/platforminputcontexts/compose/generator/qtablegenerator.h create mode 100644 src/plugins/platforminputcontexts/compose/main.cpp create mode 100644 src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp create mode 100644 src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.h create mode 100644 src/plugins/platforminputcontexts/compose/xkbcommon_workaround.h (limited to 'src/plugins/platforminputcontexts/compose') diff --git a/src/plugins/platforminputcontexts/compose/compose.json b/src/plugins/platforminputcontexts/compose/compose.json new file mode 100644 index 0000000000..2daf89ed30 --- /dev/null +++ b/src/plugins/platforminputcontexts/compose/compose.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "compose" ] +} diff --git a/src/plugins/platforminputcontexts/compose/compose.pro b/src/plugins/platforminputcontexts/compose/compose.pro new file mode 100644 index 0000000000..6387a47a4c --- /dev/null +++ b/src/plugins/platforminputcontexts/compose/compose.pro @@ -0,0 +1,20 @@ +TARGET = composeplatforminputcontextplugin + +PLUGIN_TYPE = platforminputcontexts +PLUGIN_CLASS_NAME = QComposePlatformInputContextPlugin +load(qt_plugin) + +QT += gui-private + +LIBS += $$QMAKE_LIBS_XKBCOMMON +QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_XKBCOMMON + +SOURCES += $$PWD/main.cpp \ + $$PWD/qcomposeplatforminputcontext.cpp \ + $$PWD/generator/qtablegenerator.cpp \ + +HEADERS += $$PWD/qcomposeplatforminputcontext.h \ + $$PWD/generator/qtablegenerator.h \ + $$PWD/xkbcommon_workaround.h \ + +OTHER_FILES += $$PWD/compose.json diff --git a/src/plugins/platforminputcontexts/compose/generator/qtablegenerator.cpp b/src/plugins/platforminputcontexts/compose/generator/qtablegenerator.cpp new file mode 100644 index 0000000000..5941936aec --- /dev/null +++ b/src/plugins/platforminputcontexts/compose/generator/qtablegenerator.cpp @@ -0,0 +1,402 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtablegenerator.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +//#define DEBUG_GENERATOR + +TableGenerator::TableGenerator() : m_state(NoErrors), + m_systemComposeDir(QString()) +{ + initPossibleLocations(); + findComposeFile(); + orderComposeTable(); +#ifdef DEBUG_GENERATOR + printComposeTable(); +#endif +} + +void TableGenerator::initPossibleLocations() +{ + // AFAICT there is no way to know the exact location + // of the compose files. It depends on how Xlib was configured + // on a specific platform. During the "./configure" process + // xlib generates a config.h file which contains a bunch of defines, + // including XLOCALEDIR which points to the location of the compose file dir. + // To add an extra system path use the QTCOMPOSE environment variable + if (qEnvironmentVariableIsSet("QTCOMPOSE")) { + m_possibleLocations.append(QString(qgetenv("QTCOMPOSE"))); + } + m_possibleLocations.append(QStringLiteral("/usr/share/X11/locale")); + m_possibleLocations.append(QStringLiteral("/usr/lib/X11/locale")); +} + +void TableGenerator::findComposeFile() +{ + bool found = false; + // check if XCOMPOSEFILE points to a Compose file + if (qEnvironmentVariableIsSet("XCOMPOSEFILE")) { + QString composeFile(qgetenv("XCOMPOSEFILE")); + if (composeFile.endsWith(QLatin1String("Compose"))) + found = processFile(composeFile); + else + qWarning("Qt Warning: XCOMPOSEFILE doesn't point to a valid Compose file"); +#ifdef DEBUG_GENERATOR + if (found) + qDebug() << "Using Compose file from: " << composeFile; +#endif + } + + // check if user’s home directory has a file named .XCompose + if (!found && cleanState()) { + QString composeFile = qgetenv("HOME") + QStringLiteral("/.XCompose"); + if (QFile(composeFile).exists()) + found = processFile(composeFile); +#ifdef DEBUG_GENERATOR + if (found) + qDebug() << "Using Compose file from: " << composeFile; +#endif + } + + // check for the system provided compose files + if (!found && cleanState()) { + readLocaleMappings(); + + if (cleanState()) { + + QString table = m_localeToTable.value(locale().toUpper()); + if (table.isEmpty()) + // no table mappings for the system's locale in the compose.dir + m_state = UnsupportedLocale; + else + found = processFile(systemComposeDir() + QLatin1String("/") + table); +#ifdef DEBUG_GENERATOR + if (found) + qDebug() << "Using Compose file from: " << + systemComposeDir() + QLatin1String("/") + table; +#endif + } + } + + if (found && m_composeTable.isEmpty()) + m_state = EmptyTable; + + if (!found) + m_state = MissingComposeFile; +} + +bool TableGenerator::findSystemComposeDir() +{ + bool found = false; + for (int i = 0; i < m_possibleLocations.size(); ++i) { + QString path = m_possibleLocations.at(i); + if (QFile(path + QLatin1String("/compose.dir")).exists()) { + m_systemComposeDir = path; + found = true; + break; + } + } + + if (!found) { + // should we ask to report this in the qt bug tracker? + m_state = UnknownSystemComposeDir; + qWarning("Qt Warning: Could not find a location of the system's Compose files. " + "Consider setting the QTCOMPOSE environment variable."); + } + + return found; +} + +QString TableGenerator::systemComposeDir() +{ + if (m_systemComposeDir.isNull() + && !findSystemComposeDir()) { + return QLatin1String("$QTCOMPOSE"); + } + + return m_systemComposeDir; +} + +QString TableGenerator::locale() const +{ + char *name = setlocale(LC_CTYPE, (char *)0); + return QLatin1String(name); +} + +void TableGenerator::readLocaleMappings() +{ + QFile mappings(systemComposeDir() + QLatin1String("/compose.dir")); + if (mappings.exists()) { + mappings.open(QIODevice::ReadOnly); + QTextStream in(&mappings); + // formating of compose.dir has some inconsistencies + while (!in.atEnd()) { + QString line = in.readLine(); + if (!line.startsWith("#") && line.size() != 0 && + line.at(0).isLower()) { + + QStringList pair = line.split(QRegExp(QLatin1String("\\s+"))); + QString table = pair.at(0); + if (table.endsWith(QLatin1String(":"))) + table.remove(table.size() - 1, 1); + + m_localeToTable.insert(pair.at(1).toUpper(), table); + } + } + mappings.close(); + } +} + +bool TableGenerator::processFile(QString composeFileName) +{ + QFile composeFile(composeFileName); + if (composeFile.exists()) { + composeFile.open(QIODevice::ReadOnly); + parseComposeFile(&composeFile); + return true; + } + qWarning() << QString(QLatin1String("Qt Warning: Compose file: \"%1\" can't be found")) + .arg(composeFile.fileName()); + return false; +} + +TableGenerator::~TableGenerator() +{ +} + +QList TableGenerator::composeTable() const +{ + return m_composeTable; +} + +void TableGenerator::parseComposeFile(QFile *composeFile) +{ +#ifdef DEBUG_GENERATOR + qDebug() << "TableGenerator::parseComposeFile: " << composeFile->fileName(); +#endif + QTextStream in(composeFile); + + while (!in.atEnd()) { + QString line = in.readLine(); + if (line.startsWith(QLatin1String("<"))) { + parseKeySequence(line); + } else if (line.startsWith(QLatin1String("include"))) { + parseIncludeInstruction(line); + } + } + + composeFile->close(); +} + +void TableGenerator::parseIncludeInstruction(QString line) +{ + // Parse something that looks like: + // include "/usr/share/X11/locale/en_US.UTF-8/Compose" + QString quote = QStringLiteral("\""); + line.remove(0, line.indexOf(quote) + 1); + line.chop(line.length() - line.indexOf(quote)); + + // expand substitutions if present + line.replace(QLatin1String("%H"), QString(qgetenv("HOME"))); + line.replace(QLatin1String("%L"), locale()); + line.replace(QLatin1String("%S"), systemComposeDir()); + + processFile(line); +} + +ushort TableGenerator::keysymToUtf8(uint32_t sym) +{ + QByteArray chars; + int bytes; + chars.resize(8); + + if (needWorkaround(sym)) { + uint32_t codepoint; + if (sym == XKB_KEY_KP_Space) + codepoint = XKB_KEY_space & 0x7f; + else + codepoint = sym & 0x7f; + + bytes = utf32_to_utf8(codepoint, chars.data()); + } else { + bytes = xkb_keysym_to_utf8(sym, chars.data(), chars.size()); + } + + if (bytes == -1) + qWarning("TableGenerator::keysymToUtf8 - buffer too small"); + + chars.resize(bytes-1); + +#ifdef DEBUG_GENERATOR + QTextCodec *codec = QTextCodec::codecForLocale(); + qDebug() << QString("keysym - 0x%1 : utf8 - %2").arg(QString::number(sym, 16)) + .arg(codec->toUnicode(chars)); +#endif + const QChar *ch = QString(chars.data()).unicode(); + return ch->unicode(); +} + +uint32_t TableGenerator::stringToKeysym(QString keysymName) +{ + uint32_t keysym; + const char *name = keysymName.toLatin1().constData(); + + if ((keysym = xkb_keysym_from_name(name, (xkb_keysym_flags)0)) == XKB_KEY_NoSymbol) + qWarning() << QString("Qt Warrning - invalid keysym: %1").arg(keysymName); + + return keysym; +} + +void TableGenerator::parseKeySequence(QString line) +{ + // we are interested in the lines with the following format: + // : "♬" U266c # BEAMED SIXTEENTH NOTE + int keysEnd = line.indexOf(QLatin1String(":")); + QString keys = line.left(keysEnd).trimmed(); + + // find the key sequence + QString regexp = QStringLiteral("<[^>]+>"); + QRegularExpression reg(regexp); + QRegularExpressionMatchIterator i = reg.globalMatch(keys); + QStringList keyList; + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + QString word = match.captured(0); + keyList << word; + } + + QComposeTableElement elem; + QString quote = QStringLiteral("\""); + // find the composed value - strings may be direct text encoded in the locale + // for which the compose file is to be used, or an escaped octal or hexadecimal + // character code. Octal codes are specified as "\123" and hexadecimal codes as "\0x123a". + int composeValueIndex = line.indexOf(quote, keysEnd) + 1; + const QChar valueType(line.at(composeValueIndex)); + + if (valueType == '\\' && line.at(composeValueIndex + 1).isDigit()) { + // handle octal and hex code values + QChar detectBase(line.at(composeValueIndex + 2)); + QString codeValue = line.mid(composeValueIndex + 1, line.lastIndexOf(quote) - composeValueIndex - 1); + if (detectBase == 'x') { + // hexadecimal character code + elem.value = keysymToUtf8(codeValue.toUInt(0, 16)); + } else { + // octal character code + QString hexStr = QString::number(codeValue.toUInt(0, 8), 16); + elem.value = keysymToUtf8(hexStr.toUInt(0, 16)); + } + } else { + // handle direct text encoded in the locale + elem.value = valueType.unicode(); + } + + // find the comment + int commnetIndex = line.lastIndexOf(quote) + 1; + elem.comment = line.mid(commnetIndex).trimmed(); + + // Convert to X11 keysym + int count = keyList.length(); + for (int i = 0; i < QT_KEYSEQUENCE_MAX_LEN; i++) { + if (i < count) { + QString keysym = keyList.at(i); + keysym.remove(keysym.length() - 1, 1); + keysym.remove(0, 1); + + if (keysym == QLatin1String("dead_inverted_breve")) + keysym = QStringLiteral("dead_invertedbreve"); + else if (keysym == QLatin1String("dead_double_grave")) + keysym = QStringLiteral("dead_doublegrave"); + + elem.keys[i] = stringToKeysym(keysym); + } else { + elem.keys[i] = 0; + } + } + m_composeTable.append(elem); +} + +void TableGenerator::printComposeTable() const +{ + if (composeTable().isEmpty()) + return; + + QString output; + QComposeTableElement elem; + QString comma = QStringLiteral(","); + int tableSize = m_composeTable.size(); + for (int i = 0; i < tableSize; ++i) { + elem = m_composeTable.at(i); + output.append(QLatin1String("{ {")); + for (int j = 0; j < QT_KEYSEQUENCE_MAX_LEN; j++) { + output.append(QString(QLatin1String("0x%1, ")).arg(QString::number(elem.keys[j],16))); + } + // take care of the trailing comma + if (i == tableSize - 1) + comma = QStringLiteral(""); + output.append(QString(QLatin1String("}, 0x%1, \"\" }%2 // %3 \n")) + .arg(QString::number(elem.value,16)) + .arg(comma) + .arg(elem.comment)); + } + + qDebug() << "output: \n" << output; +} + +void TableGenerator::orderComposeTable() +{ + // Stable-sorting to ensure that the item that appeared before the other in the + // original container will still appear first after the sort. This property is + // needed to handle the cases when user re-defines already defined key sequence + qStableSort(m_composeTable.begin(), m_composeTable.end(), Compare()); +} + diff --git a/src/plugins/platforminputcontexts/compose/generator/qtablegenerator.h b/src/plugins/platforminputcontexts/compose/generator/qtablegenerator.h new file mode 100644 index 0000000000..11e7b2b422 --- /dev/null +++ b/src/plugins/platforminputcontexts/compose/generator/qtablegenerator.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTABLEGENERATOR_H +#define QTABLEGENERATOR_H + +#include +#include +#include +#include + +#define QT_KEYSEQUENCE_MAX_LEN 6 + +struct QComposeTableElement { + uint keys[QT_KEYSEQUENCE_MAX_LEN]; + uint value; + QString comment; +}; + +class Compare +{ +public: + bool operator () (const QComposeTableElement &lhs, const uint rhs[QT_KEYSEQUENCE_MAX_LEN]) + { + for (size_t i = 0; i < QT_KEYSEQUENCE_MAX_LEN; i++) { + if (lhs.keys[i] != rhs[i]) + return (lhs.keys[i] < rhs[i]); + } + return false; + } + + bool operator () (const QComposeTableElement &lhs, const QComposeTableElement &rhs) + { + for (size_t i = 0; i < QT_KEYSEQUENCE_MAX_LEN; i++) { + if (lhs.keys[i] != rhs.keys[i]) + return (lhs.keys[i] < rhs.keys[i]); + } + return false; + } +}; + +class TableGenerator +{ + +public: + enum TableState + { + UnsupportedLocale, + EmptyTable, + UnknownSystemComposeDir, + MissingComposeFile, + NoErrors + }; + + TableGenerator(); + ~TableGenerator(); + + void parseComposeFile(QFile *composeFile); + void printComposeTable() const; + void orderComposeTable(); + + QList composeTable() const; + TableState tableState() const { return m_state; } + +protected: + bool processFile(QString composeFileName); + void parseKeySequence(QString line); + void parseIncludeInstruction(QString line); + + void findComposeFile(); + bool findSystemComposeDir(); + QString systemComposeDir(); + + ushort keysymToUtf8(uint32_t sym); + uint32_t stringToKeysym(QString keysymName); + + void readLocaleMappings(); + void initPossibleLocations(); + bool cleanState() const { return ((m_state & NoErrors) == NoErrors); } + QString locale() const; + +private: + QList m_composeTable; + QMap m_localeToTable; + TableState m_state; + QString m_systemComposeDir; + QList m_possibleLocations; +}; + +#endif // QTABLEGENERATOR_H diff --git a/src/plugins/platforminputcontexts/compose/main.cpp b/src/plugins/platforminputcontexts/compose/main.cpp new file mode 100644 index 0000000000..728c60caf5 --- /dev/null +++ b/src/plugins/platforminputcontexts/compose/main.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include + +#include "qcomposeplatforminputcontext.h" + +QT_BEGIN_NAMESPACE + +class QComposePlatformInputContextPlugin : public QPlatformInputContextPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QPlatformInputContextFactoryInterface" FILE "compose.json") + +public: + QComposeInputContext *create(const QString &, const QStringList &); +}; + +QComposeInputContext *QComposePlatformInputContextPlugin::create(const QString &system, const QStringList ¶mList) +{ + Q_UNUSED(paramList); + + if (system.compare(system, QStringLiteral("compose"), Qt::CaseInsensitive) == 0) + return new QComposeInputContext; + return 0; +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp b/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp new file mode 100644 index 0000000000..433c9eec37 --- /dev/null +++ b/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp @@ -0,0 +1,268 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcomposeplatforminputcontext.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +//#define DEBUG_COMPOSING + +static const int ignoreKeys[] = { + Qt::Key_Shift, + Qt::Key_Control, + Qt::Key_Meta, + Qt::Key_Alt, + Qt::Key_CapsLock, + Qt::Key_Super_L, + Qt::Key_Super_R, + Qt::Key_Hyper_L, + Qt::Key_Hyper_R, + Qt::Key_Mode_switch +}; + +static const int composingKeys[] = { + Qt::Key_Multi_key, + Qt::Key_Dead_Grave, + Qt::Key_Dead_Acute, + Qt::Key_Dead_Circumflex, + Qt::Key_Dead_Tilde, + Qt::Key_Dead_Macron, + Qt::Key_Dead_Breve, + Qt::Key_Dead_Abovedot, + Qt::Key_Dead_Diaeresis, + Qt::Key_Dead_Abovering, + Qt::Key_Dead_Doubleacute, + Qt::Key_Dead_Caron, + Qt::Key_Dead_Cedilla, + Qt::Key_Dead_Ogonek, + Qt::Key_Dead_Iota, + Qt::Key_Dead_Voiced_Sound, + Qt::Key_Dead_Semivoiced_Sound, + Qt::Key_Dead_Belowdot, + Qt::Key_Dead_Hook, + Qt::Key_Dead_Horn +}; + +QComposeInputContext::QComposeInputContext() +{ + TableGenerator reader; + m_tableState = reader.tableState(); + + if ((m_tableState & TableGenerator::NoErrors) == TableGenerator::NoErrors) { + m_composeTable = reader.composeTable(); + clearComposeBuffer(); + } +} + +bool QComposeInputContext::filterEvent(const QEvent *event) +{ + // if there were errors when generating the compose table input + // context should not try to filter anything, simply return false + if ((m_tableState & TableGenerator::NoErrors) != TableGenerator::NoErrors) + return false; + + QKeyEvent *keyEvent = (QKeyEvent *)event; + // should pass only the key presses + if (keyEvent->type() != QEvent::KeyPress) { + return false; + } + + int keyval = keyEvent->key(); + int keysym = 0; + + if (ignoreKey(keyval)) + return false; + + QString text = keyEvent->text(); + if (!composeKey(keyval) && text.isEmpty()) + return false; + + keysym = keyEvent->nativeVirtualKey(); + + int nCompose = 0; + while (m_composeBuffer[nCompose] != 0 && nCompose < QT_KEYSEQUENCE_MAX_LEN) + nCompose++; + + if (nCompose == QT_KEYSEQUENCE_MAX_LEN) { + reset(); + nCompose = 0; + } + + m_composeBuffer[nCompose] = keysym; + // check sequence + if (checkComposeTable()) + return true; + + return false; +} + +bool QComposeInputContext::isValid() const +{ + return true; +} + +void QComposeInputContext::setFocusObject(QObject *object) +{ + m_focusObject = object; +} + +void QComposeInputContext::reset() +{ + clearComposeBuffer(); +} + +void QComposeInputContext::update(Qt::InputMethodQueries q) +{ + QPlatformInputContext::update(q); +} + +static bool isDuplicate(const QComposeTableElement &lhs, const QComposeTableElement &rhs) +{ + for (size_t i = 0; i < QT_KEYSEQUENCE_MAX_LEN; i++) { + if (lhs.keys[i] != rhs.keys[i]) + return false; + } + return true; +} + +bool QComposeInputContext::checkComposeTable() +{ + QList::iterator it = + qLowerBound(m_composeTable.begin(), m_composeTable.end(), m_composeBuffer, Compare()); + + // prevent dereferencing an 'end' iterator, which would result in a crash + if (it == m_composeTable.end()) + it -= 1; + + QComposeTableElement elem = *it; + // would be nicer if qLowerBound had API that tells if the item was actually found + if (m_composeBuffer[0] != elem.keys[0]) { +#ifdef DEBUG_COMPOSING + qDebug( "### no match ###" ); +#endif + reset(); + return false; + } + // check if compose buffer is matched + for (int i=0; i < QT_KEYSEQUENCE_MAX_LEN; i++) { + + // check if partial match + if (m_composeBuffer[i] == 0 && elem.keys[i]) { +#ifdef DEBUG_COMPOSING + qDebug("### partial match ###"); +#endif + return true; + } + + if (m_composeBuffer[i] != elem.keys[i]) { +#ifdef DEBUG_COMPOSING + qDebug("### different entry ###"); +#endif + reset(); + return i != 0; + } + } +#ifdef DEBUG_COMPOSING + qDebug("### match exactly ###"); +#endif + + // check if the key sequence is overwriten - see the comment in + // TableGenerator::orderComposeTable() + int next = 1; + do { + // if we are at the end of the table, then we have nothing to do here + if (it + next != m_composeTable.end()) { + QComposeTableElement nextElem = *(it + next); + if (isDuplicate(elem, nextElem)) { + elem = nextElem; + next++; + continue; + } else { + break; + } + } + break; + } while (true); + + commitText(elem.value); + reset(); + + return true; +} + +void QComposeInputContext::commitText(uint character) const +{ + QInputMethodEvent event; + event.setCommitString(QChar(character)); + QCoreApplication::sendEvent(m_focusObject, &event); +} + +bool QComposeInputContext::ignoreKey(int keyval) const +{ + for (uint i = 0; i < (sizeof(ignoreKeys) / sizeof(ignoreKeys[0])); i++) + if (keyval == ignoreKeys[i]) + return true; + + return false; +} + +bool QComposeInputContext::composeKey(int keyval) const +{ + for (uint i = 0; i < (sizeof(composingKeys) / sizeof(composingKeys[0])); i++) + if (keyval == composingKeys[i]) + return true; + + return false; +} + +void QComposeInputContext::clearComposeBuffer() +{ + for (uint i=0; i < (sizeof(m_composeBuffer) / sizeof(int)); i++) + m_composeBuffer[i] = 0; +} + +QComposeInputContext::~QComposeInputContext() {} + +QT_END_NAMESPACE diff --git a/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.h b/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.h new file mode 100644 index 0000000000..1ced2f8ded --- /dev/null +++ b/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOMPOSEPLATFORMINPUTCONTEXT_H +#define QCOMPOSEPLATFORMINPUTCONTEXT_H + +#include + +#include + +#include "generator/qtablegenerator.h" + +QT_BEGIN_NAMESPACE + +class QEvent; + +class QComposeInputContext : public QPlatformInputContext +{ + Q_OBJECT + +public: + QComposeInputContext(); + ~QComposeInputContext(); + + bool isValid() const; + void setFocusObject(QObject *object); + void reset(); + void update(Qt::InputMethodQueries); + bool filterEvent(const QEvent *event); + +protected: + void clearComposeBuffer(); + bool ignoreKey(int keyval) const; + bool composeKey(int keyval) const; + bool checkComposeTable(); + void commitText(uint character) const; + +private: + QObject *m_focusObject; + QList m_composeTable; + uint m_composeBuffer[QT_KEYSEQUENCE_MAX_LEN + 1]; + TableGenerator::TableState m_tableState; +}; + +QT_END_NAMESPACE + +#endif // QCOMPOSEPLATFORMINPUTCONTEXT_H diff --git a/src/plugins/platforminputcontexts/compose/xkbcommon_workaround.h b/src/plugins/platforminputcontexts/compose/xkbcommon_workaround.h new file mode 100644 index 0000000000..58ce143978 --- /dev/null +++ b/src/plugins/platforminputcontexts/compose/xkbcommon_workaround.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef XKBCOMMON_WORKAROUND_H +#define XKBCOMMON_WORKAROUND_H + +// Function utf32_to_utf8() is borrowed from the libxkbcommon library, +// file keysym-utf.c. The workaround should be removed once the fix from +// https://bugs.freedesktop.org/show_bug.cgi?id=56780 gets released. +static int utf32_to_utf8(uint32_t unichar, char *buffer) +{ + int count, shift, length; + uint8_t head; + + if (unichar <= 0x007f) { + buffer[0] = unichar; + buffer[1] = '\0'; + return 2; + } + else if (unichar <= 0x07FF) { + length = 2; + head = 0xc0; + } + else if (unichar <= 0xffff) { + length = 3; + head = 0xe0; + } + else if (unichar <= 0x1fffff) { + length = 4; + head = 0xf0; + } + else if (unichar <= 0x3ffffff) { + length = 5; + head = 0xf8; + } + else { + length = 6; + head = 0xfc; + } + + for (count = length - 1, shift = 0; count > 0; count--, shift += 6) + buffer[count] = 0x80 | ((unichar >> shift) & 0x3f); + + buffer[0] = head | ((unichar >> shift) & 0x3f); + buffer[length] = '\0'; + + return length + 1; +} + +static bool needWorkaround(uint32_t sym) +{ + /* patch encoding botch */ + if (sym == XKB_KEY_KP_Space) + return true; + + /* special keysyms */ + if ((sym >= XKB_KEY_BackSpace && sym <= XKB_KEY_Clear) || + (sym >= XKB_KEY_KP_Multiply && sym <= XKB_KEY_KP_9) || + sym == XKB_KEY_Return || sym == XKB_KEY_Escape || + sym == XKB_KEY_Delete || sym == XKB_KEY_KP_Tab || + sym == XKB_KEY_KP_Enter || sym == XKB_KEY_KP_Equal) + return true; + + return false; +} + +#endif // XKBCOMMON_WORKAROUND_H -- cgit v1.2.3