|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (developer.feedback@nokia.com) |
|
6 ** |
|
7 ** This file is part of the HbCore module of the UI Extensions for Mobile. |
|
8 ** |
|
9 ** GNU Lesser General Public License Usage |
|
10 ** This file may be used under the terms of the GNU Lesser General Public |
|
11 ** License version 2.1 as published by the Free Software Foundation and |
|
12 ** appearing in the file LICENSE.LGPL included in the packaging of this file. |
|
13 ** Please review the following information to ensure the GNU Lesser General |
|
14 ** Public License version 2.1 requirements will be met: |
|
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
16 ** |
|
17 ** In addition, as a special exception, Nokia gives you certain additional |
|
18 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
20 ** |
|
21 ** If you have questions regarding the use of this file, please contact |
|
22 ** Nokia at developer.feedback@nokia.com. |
|
23 ** |
|
24 ****************************************************************************/ |
|
25 #include <QInputContextPlugin> |
|
26 #include <QLocale> |
|
27 #include <QFileSystemWatcher> |
|
28 #include <QLibrary> |
|
29 #include <QPluginLoader> |
|
30 #include <QDir> |
|
31 |
|
32 #include "hbinputmodecache_p.h" |
|
33 #include "hbinpututils.h" |
|
34 #include "hbinputmethod.h" |
|
35 #include "hbinputsettingproxy.h" |
|
36 #include "hbinputmodeproperties.h" |
|
37 #include "hbinputkeymapfactory.h" |
|
38 #include "hbinputmethod_p.h" |
|
39 #include "hbinputmethodnull_p.h" |
|
40 |
|
41 /*! |
|
42 @alpha |
|
43 @hbcore |
|
44 \class HbInputModeCache |
|
45 \brief Input framework's internal input mode resolver class. |
|
46 */ |
|
47 |
|
48 /// @cond |
|
49 |
|
50 class HbInputMethodListItem |
|
51 { |
|
52 public: |
|
53 HbInputMethodListItem() : cached(0), toBeRemoved(false) {} |
|
54 bool operator==(const HbInputMethodListItem &item) const { |
|
55 return (descriptor.pluginNameAndPath() == item.descriptor.pluginNameAndPath() && |
|
56 descriptor.key() == item.descriptor.key()); |
|
57 } |
|
58 |
|
59 public: |
|
60 HbInputMethodDescriptor descriptor; |
|
61 QStringList languages; |
|
62 HbInputMethod *cached; |
|
63 bool toBeRemoved; |
|
64 }; |
|
65 |
|
66 class HbInputModeCachePrivate |
|
67 { |
|
68 public: |
|
69 HbInputModeCachePrivate() : mWatcher(new QFileSystemWatcher()), mShuttingDown(false) {} |
|
70 ~HbInputModeCachePrivate() {} |
|
71 void refresh(const QString &directory = QString()); |
|
72 QInputContextPlugin *pluginInstance(const QString& pluginFileName) const; |
|
73 HbInputMethod *methodInstance(const QString &pluginFileName, const QString &key) const; |
|
74 HbInputModeProperties propertiesFromString(const QString &entry) const; |
|
75 HbInputModeProperties propertiesFromState(const HbInputState &state) const; |
|
76 HbInputMethod *cachedMethod(HbInputMethodListItem &item); |
|
77 void updateMonitoredPaths(); |
|
78 |
|
79 public: |
|
80 QFileSystemWatcher *mWatcher; |
|
81 QList<HbInputMethodListItem> mMethods; |
|
82 bool mShuttingDown; |
|
83 }; |
|
84 |
|
85 QInputContextPlugin* HbInputModeCachePrivate::pluginInstance(const QString& pluginFileName) const |
|
86 { |
|
87 if (QLibrary::isLibrary(pluginFileName)) { |
|
88 QPluginLoader loader(pluginFileName); |
|
89 QObject* plugin = loader.instance(); |
|
90 if (plugin) { |
|
91 return qobject_cast<QInputContextPlugin*>(plugin); |
|
92 } |
|
93 } |
|
94 |
|
95 return 0; |
|
96 } |
|
97 |
|
98 HbInputMethod *HbInputModeCachePrivate::methodInstance(const QString &pluginFileName, const QString &key) const |
|
99 { |
|
100 QInputContextPlugin *plugin = pluginInstance(pluginFileName); |
|
101 if (plugin) { |
|
102 QInputContext *instance = plugin->create(key); |
|
103 HbInputMethod *result = qobject_cast<HbInputMethod*>(instance); |
|
104 if (result) { |
|
105 QStringList languages = plugin->languages(key); |
|
106 QList<HbInputModeProperties> modeList; |
|
107 foreach (QString language, languages) { |
|
108 modeList.append(propertiesFromString(language)); |
|
109 } |
|
110 result->d_ptr->mInputModes = modeList; |
|
111 } |
|
112 return result; |
|
113 } |
|
114 |
|
115 return 0; |
|
116 } |
|
117 |
|
118 void HbInputModeCachePrivate::refresh(const QString &directory) |
|
119 { |
|
120 Q_UNUSED(directory); |
|
121 // optimize later so that if the directory is given, only changes concerning |
|
122 // it are taken into account. |
|
123 |
|
124 // First go through all the previously found entries and |
|
125 // tag them not refreshed. |
|
126 for (int k = 0; k < mMethods.count(); k++) { |
|
127 mMethods[k].toBeRemoved = true; |
|
128 } |
|
129 |
|
130 // Query plugin paths and scan the folders. |
|
131 QStringList folders = HbInputSettingProxy::instance()->inputMethodPluginPaths(); |
|
132 foreach (QString folder, folders) { |
|
133 QDir dir(folder); |
|
134 for (unsigned int i = 0; i < dir.count(); i++) { |
|
135 QString path = QString(dir.absolutePath()); |
|
136 if (path.right(1) != "\\" && path.right(1) != "/") { |
|
137 path += QDir::separator(); |
|
138 } |
|
139 path += dir[i]; |
|
140 QInputContextPlugin* inputContextPlugin = pluginInstance(path); |
|
141 if (inputContextPlugin) { |
|
142 HbInputMethodListItem listItem; |
|
143 listItem.descriptor.setPluginNameAndPath(dir.absolutePath() + QDir::separator() + dir[i]); |
|
144 |
|
145 // For each found plugin, check if there is already a list item for it. |
|
146 // If not, then add one. |
|
147 QStringList contextKeys = inputContextPlugin->keys(); |
|
148 foreach (QString key, contextKeys) { |
|
149 listItem.descriptor.setKey(key); |
|
150 listItem.descriptor.setDisplayName(inputContextPlugin->displayName(key)); |
|
151 |
|
152 int index = mMethods.indexOf(listItem); |
|
153 if (index >= 0) { |
|
154 // The method is already in the list, the situation hasn't changed. |
|
155 // just tag it not to be removed. |
|
156 mMethods[index].toBeRemoved = false; |
|
157 } else { |
|
158 listItem.languages = inputContextPlugin->languages(key); |
|
159 mMethods.append(listItem); |
|
160 } |
|
161 } |
|
162 } |
|
163 } |
|
164 } |
|
165 |
|
166 // Go through the cache list and find out if some of the previous items need to be |
|
167 // removed after the refresh. |
|
168 for (int i = 0; i < mMethods.count(); i++) { |
|
169 if (mMethods[i].toBeRemoved) { |
|
170 if (mMethods[i].cached && mMethods[i].cached->isActiveMethod()) { |
|
171 // If the item to be removed happens to be the active one, |
|
172 // try to deal with the situation. |
|
173 mMethods[i].cached->forceUnfocus(); |
|
174 if (mMethods[i].descriptor.pluginNameAndPath() == HbInputSettingProxy::instance()->activeCustomInputMethod().pluginNameAndPath()) { |
|
175 // The active custom method is being removed. |
|
176 // Clear out related setting proxy values. |
|
177 HbInputSettingProxy::instance()->setActiveCustomInputMethod(HbInputMethodDescriptor()); |
|
178 } |
|
179 |
|
180 // Replace it with null input context. |
|
181 HbInputMethod *master = HbInputMethodNull::Instance(); |
|
182 master->d_ptr->mIsActive = true; |
|
183 QInputContext* proxy = master->d_ptr->newProxy(); |
|
184 qApp->setInputContext(proxy); |
|
185 } |
|
186 delete mMethods[i].cached; |
|
187 mMethods.removeAt(i); |
|
188 i--; |
|
189 } |
|
190 } |
|
191 } |
|
192 |
|
193 HbInputModeProperties HbInputModeCachePrivate::propertiesFromString(const QString &entry) const |
|
194 { |
|
195 HbInputModeProperties result; |
|
196 |
|
197 QStringList parts = entry.split(" "); |
|
198 if (parts.count() == 4) { |
|
199 // See HbInputModeProperties::toString() for details, |
|
200 QString languageStr = parts[0] + QString(" ") + parts[1]; |
|
201 HbInputLanguage language; |
|
202 language.fromString(languageStr); |
|
203 result.setLanguage(language); |
|
204 result.setInputMode((HbInputModeType)parts[2].toLong()); |
|
205 result.setKeyboard((HbKeyboardType)parts[3].toLong()); |
|
206 } |
|
207 |
|
208 return result; |
|
209 } |
|
210 |
|
211 HbInputModeProperties HbInputModeCachePrivate::propertiesFromState(const HbInputState &state) const |
|
212 { |
|
213 HbInputModeProperties result; |
|
214 |
|
215 result.setLanguage(state.language()); |
|
216 result.setKeyboard(state.keyboard()); |
|
217 result.setInputMode(state.inputMode()); |
|
218 |
|
219 return HbInputModeProperties(result); |
|
220 } |
|
221 |
|
222 HbInputMethod *HbInputModeCachePrivate::cachedMethod(HbInputMethodListItem &item) |
|
223 { |
|
224 if (!item.cached) { |
|
225 item.cached = methodInstance(item.descriptor.pluginNameAndPath(), item.descriptor.key()); |
|
226 } |
|
227 |
|
228 return item.cached; |
|
229 } |
|
230 |
|
231 void HbInputModeCachePrivate::updateMonitoredPaths() |
|
232 { |
|
233 QStringList watchedDirs = mWatcher->directories(); |
|
234 if (!watchedDirs.isEmpty()) { |
|
235 mWatcher->removePaths(watchedDirs); |
|
236 } |
|
237 |
|
238 QStringList paths = HbInputSettingProxy::instance()->inputMethodPluginPaths(); |
|
239 foreach (QString path, paths) { |
|
240 QDir dir(path); |
|
241 if (!dir.exists() && path.left(1) == "f") { |
|
242 mWatcher->addPath(QString("f:") + QDir::separator()); |
|
243 } else { |
|
244 mWatcher->addPath(path); |
|
245 } |
|
246 } |
|
247 } |
|
248 /// @endcond |
|
249 |
|
250 /*! |
|
251 Returns the singleton instance. |
|
252 */ |
|
253 HbInputModeCache* HbInputModeCache::instance() |
|
254 { |
|
255 static HbInputModeCache theCache; |
|
256 return &theCache; |
|
257 } |
|
258 |
|
259 /*! |
|
260 Construct the object. |
|
261 */ |
|
262 HbInputModeCache::HbInputModeCache() : d_ptr(new HbInputModeCachePrivate()) |
|
263 { |
|
264 Q_D(HbInputModeCache); |
|
265 |
|
266 // Start to monitor file system for changes. |
|
267 d->updateMonitoredPaths(); |
|
268 connect(d->mWatcher, SIGNAL(directoryChanged(const QString &)), this, SLOT(directoryChanged(const QString &))); |
|
269 |
|
270 d->refresh(); |
|
271 } |
|
272 |
|
273 /*! |
|
274 Destruct the object. |
|
275 */ |
|
276 HbInputModeCache::~HbInputModeCache() |
|
277 { |
|
278 delete d_ptr; |
|
279 } |
|
280 |
|
281 /*! |
|
282 This slot is called whenever a change in input method plugin file system is detected and |
|
283 the list needs to be refreshed. |
|
284 */ |
|
285 void HbInputModeCache::directoryChanged(const QString &directory) |
|
286 { |
|
287 Q_D(HbInputModeCache); |
|
288 |
|
289 d->updateMonitoredPaths(); |
|
290 |
|
291 if (!d->mShuttingDown) { |
|
292 d->refresh(directory); |
|
293 } |
|
294 } |
|
295 |
|
296 /*! |
|
297 Shuts down the object safely. This is needed mainly for singleton object. There has been a lot |
|
298 of problems related to randown singleton desctruction order and additional shutdown step is |
|
299 needed to guarantee that it will be done safely. The slot is connected to |
|
300 QCoreApplication::aboutToQuit when the framework is initialized. |
|
301 */ |
|
302 void HbInputModeCache::shutdown() |
|
303 { |
|
304 Q_D(HbInputModeCache); |
|
305 d->mShuttingDown = true; |
|
306 |
|
307 foreach (HbInputMethodListItem method, d->mMethods) { |
|
308 delete method.cached; |
|
309 method.cached = 0; |
|
310 } |
|
311 d->mMethods.clear(); |
|
312 delete d->mWatcher; |
|
313 d->mWatcher = 0; |
|
314 } |
|
315 |
|
316 /*! |
|
317 Loads given input method and caches it. |
|
318 */ |
|
319 HbInputMethod* HbInputModeCache::loadInputMethod(const HbInputMethodDescriptor &inputMethod) |
|
320 { |
|
321 Q_D(HbInputModeCache); |
|
322 |
|
323 for (int i = 0; i < d->mMethods.count(); i++) { |
|
324 if (d->mMethods[i].descriptor.pluginNameAndPath() == inputMethod.pluginNameAndPath() && |
|
325 d->mMethods[i].descriptor.key() == inputMethod.key()) { |
|
326 if (!d->mMethods[i].cached) { |
|
327 d->mMethods[i].cached = d->methodInstance(inputMethod.pluginNameAndPath(), inputMethod.key()); |
|
328 } |
|
329 |
|
330 return d->mMethods[i].cached; |
|
331 } |
|
332 } |
|
333 |
|
334 return 0; |
|
335 } |
|
336 |
|
337 /*! |
|
338 Lists custom input methods. |
|
339 */ |
|
340 QList<HbInputMethodDescriptor> HbInputModeCache::listCustomInputMethods() |
|
341 { |
|
342 Q_D(HbInputModeCache); |
|
343 |
|
344 QList<HbInputMethodDescriptor> result; |
|
345 |
|
346 foreach (HbInputMethodListItem item, d->mMethods) { |
|
347 foreach (QString language, item.languages) { |
|
348 HbInputModeProperties properties = d->propertiesFromString(language); |
|
349 if (properties.inputMode() == HbInputModeCustom) { |
|
350 result.append(item.descriptor); |
|
351 break; |
|
352 } |
|
353 } |
|
354 } |
|
355 |
|
356 return result; |
|
357 } |
|
358 |
|
359 /*! |
|
360 Find correct handler for given input state. |
|
361 */ |
|
362 HbInputMethod* HbInputModeCache::findStateHandler(const HbInputState& state) |
|
363 { |
|
364 Q_D(HbInputModeCache); |
|
365 |
|
366 HbInputModeProperties stateProperties = d->propertiesFromState(state); |
|
367 int languageRangeIndex = -1; |
|
368 |
|
369 // First check if there is a method that matches excatly (ie. also specifies |
|
370 // the language). |
|
371 for (int i = 0; i < d->mMethods.count(); i++) { |
|
372 foreach (QString language, d->mMethods[i].languages) { |
|
373 HbInputModeProperties properties = d->propertiesFromString(language); |
|
374 if (properties.language().undefined() && |
|
375 properties.keyboard() == stateProperties.keyboard() && |
|
376 properties.inputMode() == stateProperties.inputMode()) { |
|
377 // Remember the index, we'll need this in the next phase if no exact |
|
378 // match is found. |
|
379 languageRangeIndex = i; |
|
380 } |
|
381 |
|
382 if (properties.inputMode() != HbInputModeCustom) { |
|
383 if (properties == stateProperties) { |
|
384 return d->cachedMethod(d->mMethods[i]); |
|
385 } |
|
386 } |
|
387 } |
|
388 } |
|
389 |
|
390 // No exact match found. Then see if there was a method that matches to language |
|
391 // range, meaning that the language is left unspecified in which case we'll |
|
392 // use key mapping factory for matching. |
|
393 if (languageRangeIndex >= 0) { |
|
394 QList<HbInputLanguage> languages = HbKeymapFactory::instance()->availableLanguages(); |
|
395 |
|
396 foreach(HbInputLanguage language, languages) { |
|
397 // exact match is returned If the country variant is specified in state language, |
|
398 // otherwise a method that matches to only language range is returned. |
|
399 bool exactMatchFound = (stateProperties.language().variant() != QLocale::AnyCountry) ? |
|
400 (language == stateProperties.language()) : |
|
401 (language.language() == stateProperties.language().language()); |
|
402 if (exactMatchFound) { |
|
403 return d->cachedMethod(d->mMethods[languageRangeIndex]); |
|
404 } |
|
405 } |
|
406 } |
|
407 |
|
408 return 0; |
|
409 } |
|
410 |
|
411 /*! |
|
412 Returns the active input method. |
|
413 |
|
414 \sa HbInputMethod |
|
415 */ |
|
416 HbInputMethod* HbInputModeCache::activeMethod() const |
|
417 { |
|
418 Q_D(const HbInputModeCache); |
|
419 |
|
420 foreach (HbInputMethodListItem item, d->mMethods) { |
|
421 if (item.cached && item.cached->isActiveMethod()) { |
|
422 return item.cached; |
|
423 } |
|
424 } |
|
425 |
|
426 return 0; |
|
427 } |
|
428 |
|
429 /*! |
|
430 Lists available input languages. |
|
431 */ |
|
432 QList<HbInputLanguage> HbInputModeCache::listInputLanguages() const |
|
433 { |
|
434 Q_D(const HbInputModeCache); |
|
435 |
|
436 QList<HbInputLanguage> result; |
|
437 |
|
438 foreach (HbInputMethodListItem item, d->mMethods) { |
|
439 foreach (QString language, item.languages) { |
|
440 HbInputModeProperties mode = d->propertiesFromString(language); |
|
441 if (mode.inputMode() != HbInputModeCustom) { |
|
442 if (mode.language().undefined()) { |
|
443 // This is language range. Let's add everything |
|
444 // we have key mappings for. |
|
445 QList<HbInputLanguage> languages = HbKeymapFactory::instance()->availableLanguages(); |
|
446 foreach (HbInputLanguage mappedLanguage, languages) { |
|
447 if (!result.contains(mappedLanguage)) { |
|
448 result.append(mappedLanguage); |
|
449 } |
|
450 } |
|
451 } else { |
|
452 if (!result.contains(mode.language())) { |
|
453 result.append(mode.language()); |
|
454 } |
|
455 } |
|
456 } |
|
457 } |
|
458 } |
|
459 |
|
460 return result; |
|
461 } |
|
462 |
|
463 // End of file |