--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hbcore/inputfw/hbinputmodecache.cpp Mon Apr 19 14:02:13 2010 +0300
@@ -0,0 +1,463 @@
+/****************************************************************************
+**
+** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (developer.feedback@nokia.com)
+**
+** This file is part of the HbCore module of the UI Extensions for Mobile.
+**
+** GNU Lesser General Public License Usage
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at developer.feedback@nokia.com.
+**
+****************************************************************************/
+#include <QInputContextPlugin>
+#include <QLocale>
+#include <QFileSystemWatcher>
+#include <QLibrary>
+#include <QPluginLoader>
+#include <QDir>
+
+#include "hbinputmodecache_p.h"
+#include "hbinpututils.h"
+#include "hbinputmethod.h"
+#include "hbinputsettingproxy.h"
+#include "hbinputmodeproperties.h"
+#include "hbinputkeymapfactory.h"
+#include "hbinputmethod_p.h"
+#include "hbinputmethodnull_p.h"
+
+/*!
+@alpha
+@hbcore
+\class HbInputModeCache
+\brief Input framework's internal input mode resolver class.
+*/
+
+/// @cond
+
+class HbInputMethodListItem
+{
+public:
+ HbInputMethodListItem() : cached(0), toBeRemoved(false) {}
+ bool operator==(const HbInputMethodListItem &item) const {
+ return (descriptor.pluginNameAndPath() == item.descriptor.pluginNameAndPath() &&
+ descriptor.key() == item.descriptor.key());
+ }
+
+public:
+ HbInputMethodDescriptor descriptor;
+ QStringList languages;
+ HbInputMethod *cached;
+ bool toBeRemoved;
+};
+
+class HbInputModeCachePrivate
+{
+public:
+ HbInputModeCachePrivate() : mWatcher(new QFileSystemWatcher()), mShuttingDown(false) {}
+ ~HbInputModeCachePrivate() {}
+ void refresh(const QString &directory = QString());
+ QInputContextPlugin *pluginInstance(const QString& pluginFileName) const;
+ HbInputMethod *methodInstance(const QString &pluginFileName, const QString &key) const;
+ HbInputModeProperties propertiesFromString(const QString &entry) const;
+ HbInputModeProperties propertiesFromState(const HbInputState &state) const;
+ HbInputMethod *cachedMethod(HbInputMethodListItem &item);
+ void updateMonitoredPaths();
+
+public:
+ QFileSystemWatcher *mWatcher;
+ QList<HbInputMethodListItem> mMethods;
+ bool mShuttingDown;
+};
+
+QInputContextPlugin* HbInputModeCachePrivate::pluginInstance(const QString& pluginFileName) const
+{
+ if (QLibrary::isLibrary(pluginFileName)) {
+ QPluginLoader loader(pluginFileName);
+ QObject* plugin = loader.instance();
+ if (plugin) {
+ return qobject_cast<QInputContextPlugin*>(plugin);
+ }
+ }
+
+ return 0;
+}
+
+HbInputMethod *HbInputModeCachePrivate::methodInstance(const QString &pluginFileName, const QString &key) const
+{
+ QInputContextPlugin *plugin = pluginInstance(pluginFileName);
+ if (plugin) {
+ QInputContext *instance = plugin->create(key);
+ HbInputMethod *result = qobject_cast<HbInputMethod*>(instance);
+ if (result) {
+ QStringList languages = plugin->languages(key);
+ QList<HbInputModeProperties> modeList;
+ foreach (QString language, languages) {
+ modeList.append(propertiesFromString(language));
+ }
+ result->d_ptr->mInputModes = modeList;
+ }
+ return result;
+ }
+
+ return 0;
+}
+
+void HbInputModeCachePrivate::refresh(const QString &directory)
+{
+ Q_UNUSED(directory);
+ // optimize later so that if the directory is given, only changes concerning
+ // it are taken into account.
+
+ // First go through all the previously found entries and
+ // tag them not refreshed.
+ for (int k = 0; k < mMethods.count(); k++) {
+ mMethods[k].toBeRemoved = true;
+ }
+
+ // Query plugin paths and scan the folders.
+ QStringList folders = HbInputSettingProxy::instance()->inputMethodPluginPaths();
+ foreach (QString folder, folders) {
+ QDir dir(folder);
+ for (unsigned int i = 0; i < dir.count(); i++) {
+ QString path = QString(dir.absolutePath());
+ if (path.right(1) != "\\" && path.right(1) != "/") {
+ path += QDir::separator();
+ }
+ path += dir[i];
+ QInputContextPlugin* inputContextPlugin = pluginInstance(path);
+ if (inputContextPlugin) {
+ HbInputMethodListItem listItem;
+ listItem.descriptor.setPluginNameAndPath(dir.absolutePath() + QDir::separator() + dir[i]);
+
+ // For each found plugin, check if there is already a list item for it.
+ // If not, then add one.
+ QStringList contextKeys = inputContextPlugin->keys();
+ foreach (QString key, contextKeys) {
+ listItem.descriptor.setKey(key);
+ listItem.descriptor.setDisplayName(inputContextPlugin->displayName(key));
+
+ int index = mMethods.indexOf(listItem);
+ if (index >= 0) {
+ // The method is already in the list, the situation hasn't changed.
+ // just tag it not to be removed.
+ mMethods[index].toBeRemoved = false;
+ } else {
+ listItem.languages = inputContextPlugin->languages(key);
+ mMethods.append(listItem);
+ }
+ }
+ }
+ }
+ }
+
+ // Go through the cache list and find out if some of the previous items need to be
+ // removed after the refresh.
+ for (int i = 0; i < mMethods.count(); i++) {
+ if (mMethods[i].toBeRemoved) {
+ if (mMethods[i].cached && mMethods[i].cached->isActiveMethod()) {
+ // If the item to be removed happens to be the active one,
+ // try to deal with the situation.
+ mMethods[i].cached->forceUnfocus();
+ if (mMethods[i].descriptor.pluginNameAndPath() == HbInputSettingProxy::instance()->activeCustomInputMethod().pluginNameAndPath()) {
+ // The active custom method is being removed.
+ // Clear out related setting proxy values.
+ HbInputSettingProxy::instance()->setActiveCustomInputMethod(HbInputMethodDescriptor());
+ }
+
+ // Replace it with null input context.
+ HbInputMethod *master = HbInputMethodNull::Instance();
+ master->d_ptr->mIsActive = true;
+ QInputContext* proxy = master->d_ptr->newProxy();
+ qApp->setInputContext(proxy);
+ }
+ delete mMethods[i].cached;
+ mMethods.removeAt(i);
+ i--;
+ }
+ }
+}
+
+HbInputModeProperties HbInputModeCachePrivate::propertiesFromString(const QString &entry) const
+{
+ HbInputModeProperties result;
+
+ QStringList parts = entry.split(" ");
+ if (parts.count() == 4) {
+ // See HbInputModeProperties::toString() for details,
+ QString languageStr = parts[0] + QString(" ") + parts[1];
+ HbInputLanguage language;
+ language.fromString(languageStr);
+ result.setLanguage(language);
+ result.setInputMode((HbInputModeType)parts[2].toLong());
+ result.setKeyboard((HbKeyboardType)parts[3].toLong());
+ }
+
+ return result;
+}
+
+HbInputModeProperties HbInputModeCachePrivate::propertiesFromState(const HbInputState &state) const
+{
+ HbInputModeProperties result;
+
+ result.setLanguage(state.language());
+ result.setKeyboard(state.keyboard());
+ result.setInputMode(state.inputMode());
+
+ return HbInputModeProperties(result);
+}
+
+HbInputMethod *HbInputModeCachePrivate::cachedMethod(HbInputMethodListItem &item)
+{
+ if (!item.cached) {
+ item.cached = methodInstance(item.descriptor.pluginNameAndPath(), item.descriptor.key());
+ }
+
+ return item.cached;
+}
+
+void HbInputModeCachePrivate::updateMonitoredPaths()
+{
+ QStringList watchedDirs = mWatcher->directories();
+ if (!watchedDirs.isEmpty()) {
+ mWatcher->removePaths(watchedDirs);
+ }
+
+ QStringList paths = HbInputSettingProxy::instance()->inputMethodPluginPaths();
+ foreach (QString path, paths) {
+ QDir dir(path);
+ if (!dir.exists() && path.left(1) == "f") {
+ mWatcher->addPath(QString("f:") + QDir::separator());
+ } else {
+ mWatcher->addPath(path);
+ }
+ }
+}
+/// @endcond
+
+/*!
+Returns the singleton instance.
+*/
+HbInputModeCache* HbInputModeCache::instance()
+{
+ static HbInputModeCache theCache;
+ return &theCache;
+}
+
+/*!
+Construct the object.
+*/
+HbInputModeCache::HbInputModeCache() : d_ptr(new HbInputModeCachePrivate())
+{
+ Q_D(HbInputModeCache);
+
+ // Start to monitor file system for changes.
+ d->updateMonitoredPaths();
+ connect(d->mWatcher, SIGNAL(directoryChanged(const QString &)), this, SLOT(directoryChanged(const QString &)));
+
+ d->refresh();
+}
+
+/*!
+Destruct the object.
+*/
+HbInputModeCache::~HbInputModeCache()
+{
+ delete d_ptr;
+}
+
+/*!
+This slot is called whenever a change in input method plugin file system is detected and
+the list needs to be refreshed.
+*/
+void HbInputModeCache::directoryChanged(const QString &directory)
+{
+ Q_D(HbInputModeCache);
+
+ d->updateMonitoredPaths();
+
+ if (!d->mShuttingDown) {
+ d->refresh(directory);
+ }
+}
+
+/*!
+Shuts down the object safely. This is needed mainly for singleton object. There has been a lot
+of problems related to randown singleton desctruction order and additional shutdown step is
+needed to guarantee that it will be done safely. The slot is connected to
+QCoreApplication::aboutToQuit when the framework is initialized.
+*/
+void HbInputModeCache::shutdown()
+{
+ Q_D(HbInputModeCache);
+ d->mShuttingDown = true;
+
+ foreach (HbInputMethodListItem method, d->mMethods) {
+ delete method.cached;
+ method.cached = 0;
+ }
+ d->mMethods.clear();
+ delete d->mWatcher;
+ d->mWatcher = 0;
+}
+
+/*!
+Loads given input method and caches it.
+*/
+HbInputMethod* HbInputModeCache::loadInputMethod(const HbInputMethodDescriptor &inputMethod)
+{
+ Q_D(HbInputModeCache);
+
+ for (int i = 0; i < d->mMethods.count(); i++) {
+ if (d->mMethods[i].descriptor.pluginNameAndPath() == inputMethod.pluginNameAndPath() &&
+ d->mMethods[i].descriptor.key() == inputMethod.key()) {
+ if (!d->mMethods[i].cached) {
+ d->mMethods[i].cached = d->methodInstance(inputMethod.pluginNameAndPath(), inputMethod.key());
+ }
+
+ return d->mMethods[i].cached;
+ }
+ }
+
+ return 0;
+}
+
+/*!
+Lists custom input methods.
+*/
+QList<HbInputMethodDescriptor> HbInputModeCache::listCustomInputMethods()
+{
+ Q_D(HbInputModeCache);
+
+ QList<HbInputMethodDescriptor> result;
+
+ foreach (HbInputMethodListItem item, d->mMethods) {
+ foreach (QString language, item.languages) {
+ HbInputModeProperties properties = d->propertiesFromString(language);
+ if (properties.inputMode() == HbInputModeCustom) {
+ result.append(item.descriptor);
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+/*!
+ Find correct handler for given input state.
+ */
+HbInputMethod* HbInputModeCache::findStateHandler(const HbInputState& state)
+{
+ Q_D(HbInputModeCache);
+
+ HbInputModeProperties stateProperties = d->propertiesFromState(state);
+ int languageRangeIndex = -1;
+
+ // First check if there is a method that matches excatly (ie. also specifies
+ // the language).
+ for (int i = 0; i < d->mMethods.count(); i++) {
+ foreach (QString language, d->mMethods[i].languages) {
+ HbInputModeProperties properties = d->propertiesFromString(language);
+ if (properties.language().undefined() &&
+ properties.keyboard() == stateProperties.keyboard() &&
+ properties.inputMode() == stateProperties.inputMode()) {
+ // Remember the index, we'll need this in the next phase if no exact
+ // match is found.
+ languageRangeIndex = i;
+ }
+
+ if (properties.inputMode() != HbInputModeCustom) {
+ if (properties == stateProperties) {
+ return d->cachedMethod(d->mMethods[i]);
+ }
+ }
+ }
+ }
+
+ // No exact match found. Then see if there was a method that matches to language
+ // range, meaning that the language is left unspecified in which case we'll
+ // use key mapping factory for matching.
+ if (languageRangeIndex >= 0) {
+ QList<HbInputLanguage> languages = HbKeymapFactory::instance()->availableLanguages();
+
+ foreach(HbInputLanguage language, languages) {
+ // exact match is returned If the country variant is specified in state language,
+ // otherwise a method that matches to only language range is returned.
+ bool exactMatchFound = (stateProperties.language().variant() != QLocale::AnyCountry) ?
+ (language == stateProperties.language()) :
+ (language.language() == stateProperties.language().language());
+ if (exactMatchFound) {
+ return d->cachedMethod(d->mMethods[languageRangeIndex]);
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*!
+ Returns the active input method.
+
+ \sa HbInputMethod
+ */
+HbInputMethod* HbInputModeCache::activeMethod() const
+{
+ Q_D(const HbInputModeCache);
+
+ foreach (HbInputMethodListItem item, d->mMethods) {
+ if (item.cached && item.cached->isActiveMethod()) {
+ return item.cached;
+ }
+ }
+
+ return 0;
+}
+
+/*!
+Lists available input languages.
+*/
+QList<HbInputLanguage> HbInputModeCache::listInputLanguages() const
+{
+ Q_D(const HbInputModeCache);
+
+ QList<HbInputLanguage> result;
+
+ foreach (HbInputMethodListItem item, d->mMethods) {
+ foreach (QString language, item.languages) {
+ HbInputModeProperties mode = d->propertiesFromString(language);
+ if (mode.inputMode() != HbInputModeCustom) {
+ if (mode.language().undefined()) {
+ // This is language range. Let's add everything
+ // we have key mappings for.
+ QList<HbInputLanguage> languages = HbKeymapFactory::instance()->availableLanguages();
+ foreach (HbInputLanguage mappedLanguage, languages) {
+ if (!result.contains(mappedLanguage)) {
+ result.append(mappedLanguage);
+ }
+ }
+ } else {
+ if (!result.contains(mode.language())) {
+ result.append(mode.language());
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+// End of file