summaryrefslogtreecommitdiff
path: root/extra/kdeplasma-addons/fix-spell-crash.patch
blob: 75c26c6ade2f67969fddd19bfbea3798edd5a0b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
commit 124e35885b8cd1b593b7b83a070bd0bdb5758661
Author: Simeon Bird <bladud@gmail.com>
Date:   Fri Oct 19 21:16:34 2012 -0400

    Fix the plasma spellchecker's 'foreign language' support.
    Previously this caused segfaults (even if not used) because
    it called setLanguage(), which is not thread-safe, in match().
    
    Instead, this patch constructs a new speller safely for each new
    language, without deleting the old one. Old spellers are instead
    deleted on the teardown() signal.
    
    While we're at it, amend the language detection so that the user
    can type natural language names (eg, 'german') and have the
    spell-checker find the right language.
    
    REVIEW: 106244
    BUG: 303831
    BUG: 264779
    FIXED-IN: 4.9.3

diff --git a/runners/spellchecker/spellcheck.cpp b/runners/spellchecker/spellcheck.cpp
index 672732d..cc6aeb2 100644
--- a/runners/spellchecker/spellcheck.cpp
+++ b/runners/spellchecker/spellcheck.cpp
@@ -24,6 +24,7 @@
 // #include <KDebug>
 #include <KGlobal>
 #include <KIcon>
+#include <QSet>
 
 SpellCheckRunner::SpellCheckRunner(QObject* parent, const QVariantList &args)
     : Plasma::AbstractRunner(parent, args)
@@ -43,13 +44,64 @@ void SpellCheckRunner::init()
 {
     Plasma::AbstractRunner::init();
 
-    //store all language names, makes it posible to type "spell german TERM" if english locale is set
+    //Connect prepare and teardown signals
+    connect(this, SIGNAL(prepare()), this, SLOT(loaddata()));
+    connect(this, SIGNAL(teardown()), this, SLOT(destroydata()));
+}
+
+//Load a default dictionary and some locale names
+void SpellCheckRunner::loaddata()
+{
+    //Load the default speller, with the default language
+    if (!m_spellers.contains("")) {
+        m_spellers[""] = QSharedPointer<Sonnet::Speller> (new Sonnet::Speller(""));
+    }
+    //store all language names, makes it possible to type "spell german TERM" if english locale is set
+    //Need to construct a map between natual language names and names the spell-check recognises.
     KLocale *locale = KGlobal::locale();
-    QStringList codes = locale->allLanguagesList();
-    foreach (const QString &code, codes) {
-        const QString name = locale->languageCodeToName(code);
-        m_languages[name.toLower()] = code;
+    const QStringList avail = m_spellers[""]->availableLanguages();
+    //We need to filter the available languages so that we associate the natural language
+    //name (eg. 'german') with one sub-code.
+    QSet<QString> families;
+    //First get the families
+    foreach (const QString &code, avail) {
+        families +=code.left(2);
+    }
+    //Now for each family figure out which is the main code.
+    foreach (const QString &fcode,families) {
+        QStringList family = avail.filter(fcode);
+        QString code;
+        //If we only have one code, use it.
+        //If a string is the default language, use it
+        if (family.contains(m_spellers[""]->language())) {
+            code = m_spellers[""]->language();
+        } else if (fcode == QLatin1String("en")) {
+            //If the family is english, default to en_US.
+            if (family.contains("en_US")) {
+                code = QLatin1String("en_US");
+            }
+        } else if (family.contains(fcode+QLatin1String("_")+fcode.toUpper())) {
+            //If we have a speller of the form xx_XX, try that.
+            //This gets us most European languages with more than one spelling.
+            code =  fcode+QLatin1String("_")+fcode.toUpper();
+        } else {
+            //Otherwise, pick the first value as it is highest priority.
+            code = family.first();
+        }
+        //Finally, add code to the map.
+        const QString name = locale->languageCodeToName(fcode);
+        if (!name.isEmpty()) {
+            m_languages[name.toLower()] = code;
+        }
+//         kDebug() << "SPELL lang: " << fcode<< "::"<< name << "  :  " << code;
     }
+
+}
+
+void SpellCheckRunner::destroydata()
+{
+    //Clear the data arrays to save memory
+    m_spellers.clear();
 }
 
 void SpellCheckRunner::reloadConfiguration()
@@ -73,13 +125,52 @@ void SpellCheckRunner::reloadConfiguration()
     setSyntaxes(syns);
 }
 
+/* Take the input query, split into a list, and see if it contains a language to spell in.
+ * Return the empty string if we can't match a language. */
+QString SpellCheckRunner::findlang(const QStringList& terms)
+{
+    //If first term is a language code (like en_GB), set it as the spell-check language
+    if (terms.count() >= 1 && m_spellers[""]->availableLanguages().contains(terms[0])) {
+        return terms[0];
+    }
+    //If we have two terms and the first is a language name (eg 'french'),
+    //set it as the available language
+    else if (terms.count() >=2) {
+        QString code;
+        {
+            //Is this a descriptive language name?
+            QMap<QString, QString>::const_iterator it = m_languages.constFind(terms[0].toLower());
+            if (it != m_languages.constEnd()) {
+                code = *it;
+            }
+            //Maybe it is a subset of a language code?
+            else {
+                QStringList codes = QStringList(m_languages.values()).filter(terms[0]);
+                if (!codes.isEmpty()) {
+                    code = codes.first();
+                }
+            }
+        }
+
+        if (!code.isEmpty()) {
+            //We found a valid language! Check still available
+            const QStringList avail = m_spellers[""]->availableLanguages();
+            //Does the spell-checker like it?
+            if (avail.contains(code)) {
+                return code;
+            }
+        }
+        //FIXME: Support things like 'british english' or 'canadian french'
+    }
+    return QLatin1String("");
+}
+
 void SpellCheckRunner::match(Plasma::RunnerContext &context)
 {
     if (!context.isValid()) {
         return;
     }
 
-
     const QString term = context.query();
     QString query = term;
 
@@ -88,53 +179,46 @@ void SpellCheckRunner::match(Plasma::RunnerContext &context)
         if (query.left(len) != m_triggerWord) {
             return;
         }
-
-        QString language = m_speller.defaultLanguage();
         query = query.mid(len).trimmed();
-        QStringList terms = query.split(' ');
-
-        //two terms specified, check if first is a language
-        QString customLanguage;
-        if (terms.count() == 2) {
-            customLanguage = terms[0];
-            query = terms[1];
-        }
-        //three terms specified, check if first two are a language, e.g. "american english"
-        if (terms.count() == 3) {
-            customLanguage = terms[0] + ' ' + terms[1];
-            query = terms[2];
-        }
-
-        if (!customLanguage.isEmpty()) {
-            language = customLanguage;
-            m_speller.setLanguage(language);
+    }
 
-            //not valid, maybe it is a language name, not a code
-            if (!m_speller.isValid()) {
-                QHash<QString, QString>::const_iterator it = m_languages.constFind(language.toLower());
-                //is a valid language name
-                if (it != m_languages.constEnd()) {
-                    language = *it;
+    //Pointer to speller object with our chosen language
+    QSharedPointer<Sonnet::Speller> speller = m_spellers[""];
+
+    if (speller->isValid()) {
+        QStringList terms = query.split(' ', QString::SkipEmptyParts);
+        QString lang = findlang(terms);
+        //If we found a language, create a new speller object using it.
+        if (!lang.isEmpty()) {
+            //First term is the language
+            terms.removeFirst();
+            //New speller object if we don't already have one
+            if (!m_spellers.contains(lang)) {
+                QMutexLocker lock (&m_spellLock);
+                //Check nothing happened while we were acquiring the lock
+                if (!m_spellers.contains(lang)) {
+                    m_spellers[lang] = QSharedPointer<Sonnet::Speller>(new Sonnet::Speller(lang));
                 }
             }
+            speller = m_spellers[lang];
+            //Rejoin the strings
+            query = terms.join(QLatin1String(" "));
         }
-
-        m_speller.setLanguage(language);
     }
 
-    if (query.size() < 3) {
+    if (query.size() < 2) {
         return;
     }
 
     Plasma::QueryMatch match(this);
     match.setType(Plasma::QueryMatch::InformationalMatch);
 
-    if (m_speller.isValid()) {
+    if (speller->isValid()) {
         QStringList suggestions;
-        const bool correct = m_speller.checkAndSuggest(query,suggestions);
+        const bool correct = speller->checkAndSuggest(query,suggestions);
         if (correct) {
             match.setIcon(KIcon(QLatin1String( "checkbox" )));
-            match.setText(i18n("Correct"));
+            match.setText(i18n("Correct")+QLatin1String(": ")+query);
         } else {
             match.setIcon(KIcon(QLatin1String( "edit-delete" )));
             const QString recommended = i18n("Suggested words: %1", suggestions.join(i18nc("seperator for a list of words", ", ")));
diff --git a/runners/spellchecker/spellcheck.h b/runners/spellchecker/spellcheck.h
index 492c370..ca65452 100644
--- a/runners/spellchecker/spellcheck.h
+++ b/runners/spellchecker/spellcheck.h
@@ -22,6 +22,7 @@
 #include <sonnet/speller.h>
 
 #include <plasma/abstractrunner.h>
+#include <QSharedPointer>
 
 /**
  * This checks the spelling of query
@@ -41,12 +42,17 @@ public:
 
 protected slots:
     void init();
+    void loaddata();
+    void destroydata();
 
 private:
+    QString findlang(const QStringList &terms);
+
     QString m_triggerWord;
-    QHash<QString, QString> m_languages;//key=language name, value=language code
+    QMap<QString, QString> m_languages;//key=language name, value=language code
     bool m_requireTriggerWord;
-    Sonnet::Speller m_speller;
+    QMap<QString, QSharedPointer<Sonnet::Speller> > m_spellers; //spellers
+    QMutex m_spellLock; //Lock held when constructing a new speller
 };
 
 K_EXPORT_PLASMA_RUNNER(spellcheckrunner, SpellCheckRunner)