src/hbcore/inputfw/hbinputmodecache.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 04 Oct 2010 00:38:12 +0300
changeset 30 80e4d18b72f5
parent 23 e6ad4ef83b23
permissions -rw-r--r--
Revision: 201037 Kit: 201039

/****************************************************************************
**
** 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 "hbinputmodecache_p.h"

#include <QInputContextPlugin>
#include <QLocale>
#include <QFileSystemWatcher>
#include <QLibrary>
#include <QPluginLoader>
#include <QDir>

#include "hbinpututils.h"
#include "hbinputmethod.h"
#include "hbinputcontextplugin.h"
#include "hbinputsettingproxy.h"
#include "hbinputmodeproperties.h"
#include "hbinputkeymapfactory.h"
#include "hbinputmethod_p.h"
#include "hbinputmethodnull_p.h"

/*!
@alpha
\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());
    }

    void setValues(QInputContextPlugin *plugin, const QString &key);

public:
    HbInputMethodDescriptor descriptor;
    QStringList languages;
    HbInputMethod *cached;
    bool toBeRemoved;
    HbInputLanguage cachedLanguage;
};

void HbInputMethodListItem::setValues(QInputContextPlugin *plugin, const QString &key)
{
    if (plugin) {
        descriptor.setKey(key);
        descriptor.setDisplayName(plugin->displayName(key));

        HbInputContextPlugin *extension = qobject_cast<HbInputContextPlugin *>(plugin);
        if (extension) {
            descriptor.setDisplayNames(extension->displayNames(key));
            descriptor.setIcon(extension->icon(key));
            descriptor.setIcons(extension->icons(key));
        }
    }
}

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();
    bool isMappedLanguage(const HbInputLanguage &language) const;
    void refreshIfNeeded(HbInputMethodListItem &item, const HbInputLanguage &language);

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(const 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;
    }

    HbInputLanguage activeLanguage = HbInputSettingProxy::instance()->globalInputLanguage();

    // Query plugin paths and scan the folders.
    QStringList folders = HbInputSettingProxy::instance()->inputMethodPluginPaths();
    foreach(const 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(const QString &key, contextKeys) {
                    listItem.setValues(inputContextPlugin, 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);
                        listItem.cachedLanguage = activeLanguage;
                        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();
                // The active custom method is being removed.
                // Clear out related setting proxy values.
                if (mMethods[i].descriptor.pluginNameAndPath() == HbInputSettingProxy::instance()->preferredInputMethod(Qt::Horizontal).pluginNameAndPath()) {
                    HbInputSettingProxy::instance()->setPreferredInputMethod(Qt::Horizontal, HbInputMethodDescriptor());
                }
                if (mMethods[i].descriptor.pluginNameAndPath() == HbInputSettingProxy::instance()->preferredInputMethod(Qt::Vertical).pluginNameAndPath()) {
                    HbInputSettingProxy::instance()->setPreferredInputMethod(Qt::Vertical, HbInputMethodDescriptor());
                }

                // Replace it with null input context.
                HbInputMethod *master = HbInputMethodNull::Instance();
                master->d_ptr->mIsActive = true;
                QInputContext *proxy = master->d_ptr->proxy();
                if (proxy != qApp->inputContext()) {
                    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()
{
}

bool HbInputModeCachePrivate::isMappedLanguage(const HbInputLanguage &language) const
{
    return (HbKeymapFactory::instance()->keymap(language) != 0);
}

void HbInputModeCachePrivate::refreshIfNeeded(HbInputMethodListItem &item, const HbInputLanguage &language)
{
    if (item.cachedLanguage != language) {
        QInputContextPlugin *plugin = pluginInstance(item.descriptor.pluginNameAndPath());
        item.setValues(plugin, item.descriptor.key());
        item.cachedLanguage = language;
    }
}

/// @endcond

/*!
\internal
Returns the singleton instance.
*/
HbInputModeCache *HbInputModeCache::instance()
{
    static HbInputModeCache theCache;
    return &theCache;
}

/*!
\internal
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();
}

/*!
\internal
Destruct the object.
*/
HbInputModeCache::~HbInputModeCache()
{
    delete d_ptr;
}

/*!
\internal
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);
    }
}

/*!
\internal
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;
}

/*!
\internal
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;
}

/*!
\internal
Lists all custom input methods.
*/
QList<HbInputMethodDescriptor> HbInputModeCache::listCustomInputMethods()
{
    Q_D(HbInputModeCache);

    QList<HbInputMethodDescriptor> result;

    foreach(const HbInputMethodListItem &item, d->mMethods) {
        foreach(const QString &language, item.languages) {
            HbInputModeProperties properties = d->propertiesFromString(language);
            if (properties.inputMode() == HbInputModeCustom) {
                result.append(item.descriptor);
                break;
            }
        }
    }

    return result;
}

/*!
\internal
Lists custom input methods for given parameters.
*/
QList<HbInputMethodDescriptor> HbInputModeCache::listCustomInputMethods(Qt::Orientation orientation, const HbInputLanguage &language)
{
    Q_D(HbInputModeCache);

    QList<HbInputMethodDescriptor> result;

    for (int ii = 0; ii < d->mMethods.count(); ii++) {
        foreach (const QString &lang, d->mMethods[ii].languages) {
            HbInputModeProperties properties = d->propertiesFromString(lang);
            
            // Find custom methods that supports given language or any language and
            // supports given orientation
            if (properties.inputMode() == HbInputModeCustom &&
                (properties.language() == language || properties.language() == HbInputLanguage()) &&
                ((orientation == Qt::Vertical && properties.keyboard() == HbKeyboardTouchPortrait) ||
                (orientation == Qt::Horizontal && properties.keyboard() == HbKeyboardTouchLandscape))) {

                d->refreshIfNeeded(d->mMethods[ii], language);
                result.append(d->mMethods[ii].descriptor);
                break;
            }
        }
    }

    return result;
}

/*!
\internal
Returns default input method for given orientation.
*/
HbInputMethodDescriptor HbInputModeCache::defaultInputMethod(Qt::Orientation orientation)
{
    Q_D(HbInputModeCache);

    HbInputLanguage currentLanguage = HbInputSettingProxy::instance()->globalInputLanguage();
    bool mapped = d->isMappedLanguage(currentLanguage);

    for (int ii = 0; ii < d->mMethods.count(); ii++) {
        foreach (const QString &language, d->mMethods[ii].languages) {
            HbInputModeProperties properties = d->propertiesFromString(language);

            if (properties.language().undefined()) {
                // The input method reports language range but current language is not mapped
                // language. Skip this one. 
                if (!mapped) {
                    continue; 
                }
            } else {
                // The input method reports support for specific language but it is not an exact
                // match to current language. Skip this one 
                if (properties.language() != currentLanguage) {
                    // It is not direct match either.
                    continue;
                }
            }

            // Find default method that supports given orientation
            if (properties.inputMode() == HbInputModeDefault &&
                ((orientation == Qt::Vertical && properties.keyboard() == HbKeyboardTouchPortrait) ||
                (orientation == Qt::Horizontal && properties.keyboard() == HbKeyboardTouchLandscape))) {
                d->refreshIfNeeded(d->mMethods[ii], currentLanguage);
                return d->mMethods[ii].descriptor;
            }
        }
    }

    return HbInputMethodDescriptor();
}

/*!
\internal
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(const 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(const 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;
}

/*!
\internal
Returns the active input method.

\sa HbInputMethod
*/
HbInputMethod *HbInputModeCache::activeMethod() const
{
    Q_D(const HbInputModeCache);

    foreach(const HbInputMethodListItem &item, d->mMethods) {
        if (item.cached && item.cached->isActiveMethod()) {
            return item.cached;
        }
    }

    return 0;
}

/*!
\internal
Lists available input languages.
*/
QList<HbInputLanguage> HbInputModeCache::listInputLanguages() const
{
    Q_D(const HbInputModeCache);

    QList<HbInputLanguage> result;

    foreach(const HbInputMethodListItem &item, d->mMethods) {
        foreach(const 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(const HbInputLanguage &mappedLanguage, languages) {
                        if (!result.contains(mappedLanguage)) {
                            result.append(mappedLanguage);
                        }
                    }
                } else {
                    if (!result.contains(mode.language())) {
                        result.append(mode.language());
                    }
                }
            }
        }
    }

    return result;
}

/*!
\internal
Returns true if given input method is able to handle given input state.
*/
bool HbInputModeCache::acceptsState(const HbInputMethod *inputMethod, const HbInputState &state) const
{
    Q_D(const HbInputModeCache);

    foreach(const HbInputMethodListItem &item, d->mMethods) {
        if (item.cached == inputMethod) {
            foreach(const QString &language, item.languages) {
                HbInputModeProperties mode = d->propertiesFromString(language);
                // Check if keyboard type matches.
                if (mode.keyboard() == state.keyboard()) {
                    // Check if input mode matches or it is a custom input method but
                    // state's mode is not numeric.
                    if (mode.inputMode() == state.inputMode() ||
                        ((mode.inputMode() == HbInputModeCustom) &&
                         (state.inputMode() != HbInputModeNumeric))) {
                        // Check if language matches or input method supports
                        // all mapped languages and state's language is among them.
                        if (mode.language() == state.language() ||
                            (mode.language().undefined() && d->isMappedLanguage(state.language()))) {
                            return true;
                        }
                    }
                }
            }
        }
    }

    return false;
}

/*!
\internal
Returns input method descriptor for given input method. Returns empty descriptor if the framework
doesn't recognize given input method.
*/
HbInputMethodDescriptor HbInputModeCache::descriptor(const HbInputMethod *inputMethod) const
{
    Q_D(const HbInputModeCache);

    foreach(const HbInputMethodListItem &item, d->mMethods) {
        if (item.cached == inputMethod) {
            return item.descriptor;
        }
    }

    return HbInputMethodDescriptor();
}


// End of file