40 #include "hbinputmethod_p.h" |
44 #include "hbinputmethod_p.h" |
41 #include "hbinputmethodnull_p.h" |
45 #include "hbinputmethodnull_p.h" |
42 |
46 |
43 /*! |
47 /*! |
44 @alpha |
48 @alpha |
45 @hbcore |
|
46 \class HbInputModeCache |
49 \class HbInputModeCache |
47 \brief Input framework's internal input mode resolver class. |
50 \brief Input framework's internal input mode resolver class. |
48 */ |
51 */ |
49 |
52 |
50 /// @cond |
53 /// @cond |
51 |
54 |
52 class HbInputMethodListItem |
55 void HbInputMethodListItem::setValues(QInputContextPlugin *plugin, const QString &key) |
53 { |
56 { |
54 public: |
57 if (plugin) { |
55 HbInputMethodListItem() : cached(0), toBeRemoved(false) {} |
58 descriptor.setKey(key); |
56 bool operator==(const HbInputMethodListItem &item) const { |
59 descriptor.setDisplayName(plugin->displayName(key)); |
57 return (descriptor.pluginNameAndPath() == item.descriptor.pluginNameAndPath() && |
60 |
58 descriptor.key() == item.descriptor.key()); |
61 HbInputContextPlugin *extension = qobject_cast<HbInputContextPlugin *>(plugin); |
59 } |
62 if (extension) { |
60 |
63 descriptor.setDisplayNames(extension->displayNames(key)); |
61 public: |
64 descriptor.setIcon(extension->icon(key)); |
62 HbInputMethodDescriptor descriptor; |
65 descriptor.setIcons(extension->icons(key)); |
63 QStringList languages; |
66 } |
64 HbInputMethod *cached; |
67 } |
65 bool toBeRemoved; |
68 } |
66 }; |
69 |
67 |
70 QDataStream &operator<<(QDataStream &out, const HbInputMethodListItem &item) |
68 class HbInputModeCachePrivate |
71 { |
69 { |
72 out << item.descriptor; |
70 public: |
73 out << item.languages; |
71 HbInputModeCachePrivate() : mWatcher(new QFileSystemWatcher()), mShuttingDown(false) {} |
74 return out; |
72 ~HbInputModeCachePrivate() {} |
75 } |
73 void refresh(const QString &directory = QString()); |
76 |
74 QInputContextPlugin *pluginInstance(const QString &pluginFileName) const; |
77 QDataStream &operator>>(QDataStream &in, HbInputMethodListItem &item) |
75 HbInputMethod *methodInstance(const QString &pluginFileName, const QString &key) const; |
78 { |
76 HbInputModeProperties propertiesFromString(const QString &entry) const; |
79 in >> item.descriptor; |
77 HbInputModeProperties propertiesFromState(const HbInputState &state) const; |
80 in >> item.languages; |
78 HbInputMethod *cachedMethod(HbInputMethodListItem &item); |
81 item.cached = 0; |
79 void updateMonitoredPaths(); |
82 item.toBeRemoved = false; |
80 bool isMappedLanguage(const HbInputLanguage &language) const; |
83 return in; |
81 |
84 } |
82 public: |
85 |
83 QFileSystemWatcher *mWatcher; |
86 HbInputModeCachePrivate::HbInputModeCachePrivate() |
84 QList<HbInputMethodListItem> mMethods; |
87 : mSharedMethodList(0), |
85 bool mShuttingDown; |
88 mMethodListModTime(0), |
86 }; |
89 mMethodListLastUpdate(0), |
87 |
90 mMethodsFetchedFromDisk(false), |
88 QInputContextPlugin *HbInputModeCachePrivate::pluginInstance(const QString &pluginFileName) const |
91 mShuttingDown(false) |
|
92 { |
|
93 mSharedMethodList = new QSharedMemory(HbInputMethodListKey); |
|
94 // sharedMethodList is only attached when the list is updated |
|
95 mMethodListModTime = new QSharedMemory(HbInputMethodListModTimeKey); |
|
96 mMethodListModTime->attach(); |
|
97 } |
|
98 |
|
99 HbInputModeCachePrivate::~HbInputModeCachePrivate() |
|
100 { |
|
101 delete mSharedMethodList; |
|
102 delete mMethodListModTime; |
|
103 } |
|
104 |
|
105 void HbInputModeCachePrivate::refresh() |
|
106 { |
|
107 // Shared memory data is used if available (checked every time we refresh) |
|
108 // Otherwise the methods are read from disk, but just once during modecache lifetime |
|
109 if (!readInputMethodDataFromSharedMemory() && !mMethodsFetchedFromDisk) { |
|
110 readInputMethodDataFromDisk(&mMethods); |
|
111 pruneRemovedMethods(); |
|
112 mMethodsFetchedFromDisk = true; |
|
113 } |
|
114 } |
|
115 |
|
116 void HbInputModeCachePrivate::readInputMethodDataFromDisk(QList<HbInputMethodListItem>* methodList, const QDir &readPath) |
|
117 { |
|
118 bool readFromSinglePath = (readPath != QDir()); |
|
119 // First go through all the previously found entries and |
|
120 // tag them not refreshed. In case a directory is defined, only marks entries |
|
121 // in that directory |
|
122 for (int i = 0; i < methodList->size(); ++i) { |
|
123 if (readFromSinglePath) { |
|
124 if (methodList->at(i).descriptor.pluginNameAndPath().left(methodList->at(i).descriptor.pluginNameAndPath().lastIndexOf(QDir::separator())) |
|
125 == readPath.absolutePath()) { |
|
126 (*methodList)[i].toBeRemoved = true; |
|
127 } |
|
128 } else { |
|
129 (*methodList)[i].toBeRemoved = true; |
|
130 } |
|
131 } |
|
132 |
|
133 // Query plugin paths and scan the folders. |
|
134 QStringList folders = HbInputSettingProxy::instance()->inputMethodPluginPaths(); |
|
135 foreach(const QString &folder, folders) { |
|
136 QDir dir(folder); |
|
137 if (!readFromSinglePath || readPath == dir) { |
|
138 for (unsigned int i = 0; i < dir.count(); i++) { |
|
139 QString path = QString(dir.absolutePath()); |
|
140 if (path.right(1) != "\\" && path.right(1) != "/") { |
|
141 path += QDir::separator(); |
|
142 } |
|
143 path += dir[i]; |
|
144 QInputContextPlugin *inputContextPlugin = pluginInstance(path); |
|
145 if (inputContextPlugin) { |
|
146 HbInputMethodListItem listItem; |
|
147 listItem.descriptor.setPluginNameAndPath(dir.absolutePath() + QDir::separator() + dir[i]); |
|
148 |
|
149 // For each found plugin, check if there is already a list item for it. |
|
150 // If not, then add one. |
|
151 QStringList contextKeys = inputContextPlugin->keys(); |
|
152 foreach(const QString &key, contextKeys) { |
|
153 listItem.setValues(inputContextPlugin, key); |
|
154 |
|
155 int index = methodList->indexOf(listItem); |
|
156 if (index >= 0) { |
|
157 // The method is already in the list, the situation hasn't changed. |
|
158 // just tag it not to be removed. |
|
159 (*methodList)[index].toBeRemoved = false; |
|
160 } else { |
|
161 listItem.languages = inputContextPlugin->languages(key); |
|
162 methodList->append(listItem); |
|
163 } |
|
164 } |
|
165 } |
|
166 } |
|
167 } |
|
168 } |
|
169 } |
|
170 |
|
171 bool HbInputModeCachePrivate::readInputMethodDataFromSharedMemory() |
|
172 { |
|
173 // Check if the shared list has been modified |
|
174 if (!mMethodListModTime->isAttached()) { |
|
175 // Shared memory is not attached |
|
176 // Revert to in-process handling |
|
177 return false; |
|
178 } |
|
179 // No locking, since in case the value is corrupt we just need to read the list again |
|
180 // on the next run |
|
181 uint *newModTime = static_cast<uint *>(mMethodListModTime->data()); |
|
182 if (*newModTime == mMethodListLastUpdate) { |
|
183 // The internal list is still in sync with the one in shared memory |
|
184 return true; |
|
185 } |
|
186 // Modifications done since last update, try to attach the method list |
|
187 // Revert to in-process handling if not successful |
|
188 if (!mSharedMethodList->attach()) { |
|
189 return false; |
|
190 } |
|
191 // Attached successfully, update the modification time |
|
192 mMethodListLastUpdate = *newModTime; |
|
193 |
|
194 // To start updating the list, first mark all methods for removal |
|
195 for (int k = 0; k < mMethods.count(); k++) { |
|
196 mMethods[k].toBeRemoved = true; |
|
197 } |
|
198 |
|
199 // Get a copy of the list from shared memory |
|
200 mSharedMethodList->lock(); |
|
201 QByteArray array(static_cast<const char *>(mSharedMethodList->data())+sizeof(int), *static_cast<int *>(mSharedMethodList->data())); |
|
202 // array now has a copy of the data, so we can unlock and detach |
|
203 mSharedMethodList->unlock(); |
|
204 mSharedMethodList->detach(); |
|
205 |
|
206 // Next read the entries from the array to a temporary list |
|
207 QDataStream in(&array, QIODevice::ReadOnly); |
|
208 QList<HbInputMethodListItem> newMethodList; |
|
209 in >> newMethodList; |
|
210 |
|
211 // Go through the temporary list and append new methods to internal list |
|
212 // Duplicates are marked as not to be removed, the rest will be removed by pruneRemovedMethods |
|
213 foreach (const HbInputMethodListItem& item, newMethodList) { |
|
214 int index = mMethods.indexOf(item); |
|
215 if (index >= 0) { |
|
216 mMethods[index].toBeRemoved = false; |
|
217 } else { |
|
218 mMethods.append(item); |
|
219 } |
|
220 } |
|
221 pruneRemovedMethods(); |
|
222 return true; |
|
223 } |
|
224 |
|
225 void HbInputModeCachePrivate::pruneRemovedMethods() |
|
226 { |
|
227 // Go through the cache list and find out if some of the previous items need to be |
|
228 // removed after the refresh. |
|
229 for (int i = 0; i < mMethods.count(); i++) { |
|
230 if (mMethods.at(i).toBeRemoved) { |
|
231 if (mMethods.at(i).cached && mMethods.at(i).cached->isActiveMethod()) { |
|
232 // If the item to be removed happens to be the active one, |
|
233 // try to deal with the situation. |
|
234 mMethods.at(i).cached->forceUnfocus(); |
|
235 // The active custom method is being removed. |
|
236 // Clear out related setting proxy values. |
|
237 if (mMethods.at(i).descriptor.pluginNameAndPath() == HbInputSettingProxy::instance()->preferredInputMethod(Qt::Horizontal).pluginNameAndPath()) { |
|
238 HbInputSettingProxy::instance()->setPreferredInputMethod(Qt::Horizontal, HbInputMethodDescriptor()); |
|
239 } |
|
240 if (mMethods.at(i).descriptor.pluginNameAndPath() == HbInputSettingProxy::instance()->preferredInputMethod(Qt::Vertical).pluginNameAndPath()) { |
|
241 HbInputSettingProxy::instance()->setPreferredInputMethod(Qt::Vertical, HbInputMethodDescriptor()); |
|
242 } |
|
243 |
|
244 // Replace it with null input context. |
|
245 HbInputMethod *master = HbInputMethodNull::Instance(); |
|
246 master->d_ptr->mIsActive = true; |
|
247 QInputContext *proxy = master->d_ptr->proxy(); |
|
248 if (proxy != qApp->inputContext()) { |
|
249 qApp->setInputContext(proxy); |
|
250 } |
|
251 } |
|
252 delete mMethods[i].cached; |
|
253 mMethods.removeAt(i); |
|
254 i--; |
|
255 } |
|
256 } |
|
257 } |
|
258 |
|
259 QInputContextPlugin *HbInputModeCachePrivate::pluginInstance(const QString &pluginFileName) |
89 { |
260 { |
90 if (QLibrary::isLibrary(pluginFileName)) { |
261 if (QLibrary::isLibrary(pluginFileName)) { |
91 QPluginLoader loader(pluginFileName); |
262 QPluginLoader loader(pluginFileName); |
92 QObject *plugin = loader.instance(); |
263 QObject *plugin = loader.instance(); |
93 if (plugin) { |
264 if (plugin) { |
106 HbInputMethod *result = qobject_cast<HbInputMethod *>(instance); |
277 HbInputMethod *result = qobject_cast<HbInputMethod *>(instance); |
107 if (result) { |
278 if (result) { |
108 QStringList languages = plugin->languages(key); |
279 QStringList languages = plugin->languages(key); |
109 QList<HbInputModeProperties> modeList; |
280 QList<HbInputModeProperties> modeList; |
110 foreach(const QString &language, languages) { |
281 foreach(const QString &language, languages) { |
111 modeList.append(propertiesFromString(language)); |
282 modeList.append(HbInputModeProperties::fromString(language)); |
112 } |
283 } |
113 result->d_ptr->mInputModes = modeList; |
284 result->d_ptr->mInputModes = modeList; |
114 } |
285 } |
115 return result; |
286 return result; |
116 } |
287 } |
117 |
288 |
118 return 0; |
289 return 0; |
119 } |
|
120 |
|
121 void HbInputModeCachePrivate::refresh(const QString &directory) |
|
122 { |
|
123 Q_UNUSED(directory); |
|
124 // optimize later so that if the directory is given, only changes concerning |
|
125 // it are taken into account. |
|
126 |
|
127 // First go through all the previously found entries and |
|
128 // tag them not refreshed. |
|
129 for (int k = 0; k < mMethods.count(); k++) { |
|
130 mMethods[k].toBeRemoved = true; |
|
131 } |
|
132 |
|
133 // Query plugin paths and scan the folders. |
|
134 QStringList folders = HbInputSettingProxy::instance()->inputMethodPluginPaths(); |
|
135 foreach(const QString &folder, folders) { |
|
136 QDir dir(folder); |
|
137 for (unsigned int i = 0; i < dir.count(); i++) { |
|
138 QString path = QString(dir.absolutePath()); |
|
139 if (path.right(1) != "\\" && path.right(1) != "/") { |
|
140 path += QDir::separator(); |
|
141 } |
|
142 path += dir[i]; |
|
143 QInputContextPlugin *inputContextPlugin = pluginInstance(path); |
|
144 if (inputContextPlugin) { |
|
145 HbInputMethodListItem listItem; |
|
146 listItem.descriptor.setPluginNameAndPath(dir.absolutePath() + QDir::separator() + dir[i]); |
|
147 |
|
148 // For each found plugin, check if there is already a list item for it. |
|
149 // If not, then add one. |
|
150 QStringList contextKeys = inputContextPlugin->keys(); |
|
151 foreach(const QString &key, contextKeys) { |
|
152 listItem.descriptor.setKey(key); |
|
153 listItem.descriptor.setDisplayName(inputContextPlugin->displayName(key)); |
|
154 |
|
155 HbInputContextPlugin *extension = qobject_cast<HbInputContextPlugin *>(inputContextPlugin); |
|
156 if (extension) { |
|
157 listItem.descriptor.setDisplayNames(extension->displayNames(key)); |
|
158 listItem.descriptor.setIcon(extension->icon(key)); |
|
159 listItem.descriptor.setIcons(extension->icons(key)); |
|
160 } |
|
161 |
|
162 int index = mMethods.indexOf(listItem); |
|
163 if (index >= 0) { |
|
164 // The method is already in the list, the situation hasn't changed. |
|
165 // just tag it not to be removed. |
|
166 mMethods[index].toBeRemoved = false; |
|
167 } else { |
|
168 listItem.languages = inputContextPlugin->languages(key); |
|
169 mMethods.append(listItem); |
|
170 } |
|
171 } |
|
172 } |
|
173 } |
|
174 } |
|
175 |
|
176 // Go through the cache list and find out if some of the previous items need to be |
|
177 // removed after the refresh. |
|
178 for (int i = 0; i < mMethods.count(); i++) { |
|
179 if (mMethods[i].toBeRemoved) { |
|
180 if (mMethods[i].cached && mMethods[i].cached->isActiveMethod()) { |
|
181 // If the item to be removed happens to be the active one, |
|
182 // try to deal with the situation. |
|
183 mMethods[i].cached->forceUnfocus(); |
|
184 // The active custom method is being removed. |
|
185 // Clear out related setting proxy values. |
|
186 if (mMethods[i].descriptor.pluginNameAndPath() == HbInputSettingProxy::instance()->preferredInputMethod(Qt::Horizontal).pluginNameAndPath()) { |
|
187 HbInputSettingProxy::instance()->setPreferredInputMethod(Qt::Horizontal, HbInputMethodDescriptor()); |
|
188 } |
|
189 if (mMethods[i].descriptor.pluginNameAndPath() == HbInputSettingProxy::instance()->preferredInputMethod(Qt::Vertical).pluginNameAndPath()) { |
|
190 HbInputSettingProxy::instance()->setPreferredInputMethod(Qt::Vertical, HbInputMethodDescriptor()); |
|
191 } |
|
192 |
|
193 // Replace it with null input context. |
|
194 HbInputMethod *master = HbInputMethodNull::Instance(); |
|
195 master->d_ptr->mIsActive = true; |
|
196 QInputContext *proxy = master->d_ptr->proxy(); |
|
197 if (proxy != qApp->inputContext()) { |
|
198 qApp->setInputContext(proxy); |
|
199 } |
|
200 } |
|
201 delete mMethods[i].cached; |
|
202 mMethods.removeAt(i); |
|
203 i--; |
|
204 } |
|
205 } |
|
206 } |
|
207 |
|
208 HbInputModeProperties HbInputModeCachePrivate::propertiesFromString(const QString &entry) const |
|
209 { |
|
210 HbInputModeProperties result; |
|
211 |
|
212 QStringList parts = entry.split(' '); |
|
213 if (parts.count() == 4) { |
|
214 // See HbInputModeProperties::toString() for details, |
|
215 QString languageStr = parts[0] + QString(" ") + parts[1]; |
|
216 HbInputLanguage language; |
|
217 language.fromString(languageStr); |
|
218 result.setLanguage(language); |
|
219 result.setInputMode((HbInputModeType)parts[2].toLong()); |
|
220 result.setKeyboard((HbKeyboardType)parts[3].toLong()); |
|
221 } |
|
222 |
|
223 return result; |
|
224 } |
|
225 |
|
226 HbInputModeProperties HbInputModeCachePrivate::propertiesFromState(const HbInputState &state) const |
|
227 { |
|
228 HbInputModeProperties result; |
|
229 |
|
230 result.setLanguage(state.language()); |
|
231 result.setKeyboard(state.keyboard()); |
|
232 result.setInputMode(state.inputMode()); |
|
233 |
|
234 return HbInputModeProperties(result); |
|
235 } |
290 } |
236 |
291 |
237 HbInputMethod *HbInputModeCachePrivate::cachedMethod(HbInputMethodListItem &item) |
292 HbInputMethod *HbInputModeCachePrivate::cachedMethod(HbInputMethodListItem &item) |
238 { |
293 { |
239 if (!item.cached) { |
294 if (!item.cached) { |
427 Returns default input method for given orientation. |
437 Returns default input method for given orientation. |
428 */ |
438 */ |
429 HbInputMethodDescriptor HbInputModeCache::defaultInputMethod(Qt::Orientation orientation) |
439 HbInputMethodDescriptor HbInputModeCache::defaultInputMethod(Qt::Orientation orientation) |
430 { |
440 { |
431 Q_D(HbInputModeCache); |
441 Q_D(HbInputModeCache); |
432 |
442 d->refresh(); |
433 HbInputMethodDescriptor result; |
443 |
434 foreach (const HbInputMethodListItem &item, d->mMethods) { |
444 HbInputLanguage currentLanguage = HbInputSettingProxy::instance()->globalInputLanguage(); |
435 foreach (const QString &language, item.languages) { |
445 bool mapped = d->isMappedLanguage(currentLanguage); |
436 HbInputModeProperties properties = d->propertiesFromString(language); |
446 |
|
447 for (int i = 0; i < d->mMethods.count(); i++) { |
|
448 foreach (const QString &language, d->mMethods[i].languages) { |
|
449 HbInputModeProperties properties = HbInputModeProperties::fromString(language); |
|
450 |
|
451 if (properties.language().undefined()) { |
|
452 // The input method reports language range but current language is not mapped |
|
453 // language. Skip this one. |
|
454 if (!mapped) { |
|
455 continue; |
|
456 } |
|
457 } else { |
|
458 // The input method reports support for specific language but it is not an exact |
|
459 // match to current language. Skip this one |
|
460 if (properties.language() != currentLanguage) { |
|
461 // It is not direct match either. |
|
462 continue; |
|
463 } |
|
464 } |
437 |
465 |
438 // Find default method that supports given orientation |
466 // Find default method that supports given orientation |
439 if (properties.inputMode() == HbInputModeDefault && |
467 if (properties.inputMode() == HbInputModeDefault && |
440 ((orientation == Qt::Vertical && properties.keyboard() == HbKeyboardTouchPortrait) || |
468 ((orientation == Qt::Vertical && properties.keyboard() == HbKeyboardTouchPortrait) || |
441 (orientation == Qt::Horizontal && properties.keyboard() == HbKeyboardTouchLandscape))) { |
469 (orientation == Qt::Horizontal && properties.keyboard() == HbKeyboardTouchLandscape))) { |
442 result = item.descriptor; |
470 return d->mMethods[i].descriptor; |
443 break; |
471 } |
444 } |
472 } |
445 } |
473 } |
446 } |
474 |
447 |
475 return HbInputMethodDescriptor(); |
448 return result; |
|
449 } |
476 } |
450 |
477 |
451 /*! |
478 /*! |
452 \internal |
479 \internal |
453 Find correct handler for given input state. |
480 Find correct handler for given input state. |
454 */ |
481 */ |
455 HbInputMethod *HbInputModeCache::findStateHandler(const HbInputState &state) |
482 HbInputMethod *HbInputModeCache::findStateHandler(const HbInputState &state) |
456 { |
483 { |
457 Q_D(HbInputModeCache); |
484 Q_D(HbInputModeCache); |
458 |
485 d->refresh(); |
459 HbInputModeProperties stateProperties = d->propertiesFromState(state); |
486 |
|
487 HbInputModeProperties stateProperties(state); |
460 int languageRangeIndex = -1; |
488 int languageRangeIndex = -1; |
461 |
489 |
462 // First check if there is a method that matches excatly (ie. also specifies |
490 // First check if there is a method that matches excatly (ie. also specifies |
463 // the language). |
491 // the language). |
464 for (int i = 0; i < d->mMethods.count(); i++) { |
492 for (int i = 0; i < d->mMethods.count(); i++) { |
465 foreach(const QString &language, d->mMethods[i].languages) { |
493 foreach(const QString &language, d->mMethods[i].languages) { |
466 HbInputModeProperties properties = d->propertiesFromString(language); |
494 HbInputModeProperties properties = HbInputModeProperties::fromString(language); |
467 if (properties.language().undefined() && |
495 if (properties.language().undefined() && |
468 properties.keyboard() == stateProperties.keyboard() && |
496 properties.keyboard() == stateProperties.keyboard() && |
469 properties.inputMode() == stateProperties.inputMode()) { |
497 properties.inputMode() == stateProperties.inputMode()) { |
470 // Remember the index, we'll need this in the next phase if no exact |
498 // Remember the index, we'll need this in the next phase if no exact |
471 // match is found. |
499 // match is found. |