1 /*
2 * This file is part of SpellChecker plugin for Code::Blocks Studio
3 * Copyright (C) 2009 Daniel Anselmi
4 *
5 * SpellChecker plugin is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * SpellChecker plugin is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with SpellChecker. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19 #include "hunspell.hxx"
20 #include "HunspellInterface.h"
21 
22 #include <wx/filename.h>
23 #include <wx/tokenzr.h>
24 #include <wx/textfile.h>
25 #include <wx/config.h>
26 
HunspellInterface(wxSpellCheckUserInterface * pDlg)27 HunspellInterface::HunspellInterface(wxSpellCheckUserInterface* pDlg /* = NULL */)
28 {
29     m_pSpellUserInterface = pDlg;
30 
31     if (m_pSpellUserInterface != NULL)
32         m_pSpellUserInterface->SetSpellCheckEngine(this);
33 
34     m_pHunspell = NULL;
35     m_bPersonalDictionaryModified = false;
36 }
37 
~HunspellInterface()38 HunspellInterface::~HunspellInterface()
39 {
40     if (m_bPersonalDictionaryModified)
41     {
42         //if (wxYES == ::wxMessageBox(_T("Would you like to save any of your changes to your personal dictionary?"), _T("Save Changes"), wxYES_NO | wxICON_QUESTION))
43         m_PersonalDictionary.SavePersonalDictionary();
44     }
45 
46     UninitializeSpellCheckEngine();
47 
48     delete m_pSpellUserInterface;
49     m_pSpellUserInterface = NULL;
50 }
51 
InitializeSpellCheckEngine()52 int HunspellInterface::InitializeSpellCheckEngine()
53 {
54     UninitializeSpellCheckEngine();
55 
56     wxString strAffixFile = GetAffixFileName();
57     wxString strDictionaryFile = GetDictionaryFileName();
58 
59     if ( wxFileName::FileExists(strAffixFile) && wxFileName::FileExists(strDictionaryFile) )
60     {
61         // Prepend long path prefix to make sure Hunspell uses _wopen for Unicode path's:
62 #ifdef __WXMSW__
63         wxString lpPrefix = _T("\\\\?\\");
64 #else
65         wxString lpPrefix = wxEmptyString;
66 #endif
67         wxCharBuffer affixFileCharBuffer      = ConvertToUnicode(lpPrefix + strAffixFile);
68         wxCharBuffer dictionaryFileCharBuffer = ConvertToUnicode(lpPrefix + strDictionaryFile);
69         m_pHunspell = new Hunspell(affixFileCharBuffer, dictionaryFileCharBuffer);
70     }
71 
72     m_bEngineInitialized = (m_pHunspell != NULL);
73 
74     return m_bEngineInitialized;
75 }
76 
UninitializeSpellCheckEngine()77 int HunspellInterface::UninitializeSpellCheckEngine()
78 {
79     wxDELETE(m_pHunspell);
80     m_bEngineInitialized = false;
81     return true;
82 }
83 
SetOption(SpellCheckEngineOption & Option)84 int HunspellInterface::SetOption(SpellCheckEngineOption& Option)
85 {
86     // MySpell doesn't really have any options that I know of other than the affix and
87     // dictionary files.  To change those, a new MySpell instance must be created though
88 
89     // First make sure that either the affix or dict file have changed
90     if (Option.GetName() == _T("dictionary-path"))
91     {
92         // Dictionary path and language are now invalid, so clear them out
93         m_Options.erase(_T("dict-file"));
94         m_Options.erase(_T("affix-file"));
95 
96         m_strDictionaryPath = Option.GetValueAsString();
97         PopulateDictionaryMap(&m_DictionaryLookupMap, m_strDictionaryPath);
98         //return true;  // Even though the option didn't change, it isn't an error, so return true
99     }
100     else if (Option.GetName() == _T("language"))
101     {
102         m_Options.erase(_T("dict-file"));
103         m_Options.erase(_T("affix-file"));
104 
105         //return true;  // Even though the option didn't change, it isn't an error, so return true
106     }
107     else if (Option.GetName() == _T("affix-file"))
108     {
109         // Dictionary path and language are now invalid, so clear them out
110         m_strDictionaryPath = _T("");
111         m_Options.erase(_T("dictionary-path"));
112         m_Options.erase(_T("language"));
113     }
114     else if (Option.GetName() == _T("dict-file"))
115     {
116         // Dictionary path and language are now invalid, so clear them out
117         m_strDictionaryPath = _T("");
118         m_Options.erase(_T("dictionary-path"));
119         m_Options.erase(_T("language"));
120     }
121     else
122         return false; // We don't understand this option so return the error
123 
124     // We'll something changed so tear down the old spell check engine and create a new one
125     return InitializeSpellCheckEngine();
126 }
127 
CheckSpelling(wxString strText)128 wxString HunspellInterface::CheckSpelling(wxString strText)
129 {
130     if (m_pHunspell == NULL)
131         return wxEmptyString;
132 
133     int nDiff = 0;
134 
135     strText += _T(" ");
136 
137     wxString strDelimiters = _T(" \t\r\n.,?!@#$%^&*()-=_+[]{}\\|;:\"<>/~0123456789");
138     wxStringTokenizer tkz(strText, strDelimiters);
139     while ( tkz.HasMoreTokens() )
140     {
141         wxString token = tkz.GetNextToken();
142         int TokenStart = tkz.GetPosition() - token.Length() - 1;
143         TokenStart += nDiff;  // Take into account any changes to the size of the strText
144 
145         // process token here
146         if ( !IsWordInDictionary(token) )
147         {
148             // If this word is in the always ignore list, then just move on
149             if (m_AlwaysIgnoreList.Index(token) != wxNOT_FOUND)
150                 continue;
151 
152             bool bReplaceFromMap = false;
153             StringToStringMap::iterator WordFinder = m_AlwaysReplaceMap.find(token);
154             if (WordFinder != m_AlwaysReplaceMap.end())
155                 bReplaceFromMap = true;
156 
157             int nUserReturnValue = 0;
158 
159             if (!bReplaceFromMap)
160             {
161                 // Define the context of the word
162                 DefineContext(strText, TokenStart, token.Length());
163 
164                 // Print out the misspelling and get a replasment from the user
165                 // Present the dialog so the user can tell us what to do with this word
166                 nUserReturnValue = GetUserCorrection(token);  //Show function will show the dialog and not return until the user makes a decision
167             }
168 
169             if (nUserReturnValue == wxSpellCheckUserInterface::ACTION_CLOSE)
170                 break;
171             else if ((nUserReturnValue == wxSpellCheckUserInterface::ACTION_REPLACE) || bReplaceFromMap)
172             {
173                 wxString strReplacementText = (bReplaceFromMap) ? (*WordFinder).second : m_pSpellUserInterface->GetReplacementText();
174                 // Increase/Decreate the character difference so that the next loop is on track
175                 nDiff += strReplacementText.Length() - token.Length();
176                 // Replace the misspelled word with the replacement */
177                 strText.replace(TokenStart, token.Length(), strReplacementText);
178             }
179         }
180     }
181 
182     strText = strText.Left(strText.Len() - 1);
183 
184     return strText;
185 }
186 
GetSuggestions(const wxString & strMisspelledWord)187 wxArrayString HunspellInterface::GetSuggestions(const wxString& strMisspelledWord)
188 {
189     wxArrayString wxReturnArray;
190     wxReturnArray.Empty();
191 
192     if (m_pHunspell)
193     {
194         char **wlst;
195 
196         wxCharBuffer misspelledWordCharBuffer = ConvertToUnicode(strMisspelledWord);
197         if ( misspelledWordCharBuffer.data() != NULL)
198         {
199             int ns = m_pHunspell->suggest(&wlst, misspelledWordCharBuffer);
200             for (int i=0; i < ns; i++)
201             {
202                 wxReturnArray.Add(ConvertFromUnicode(wlst[i]));
203                 free(wlst[i]);
204             }
205             free(wlst);
206         }
207     }
208 
209     return wxReturnArray;
210 }
211 
IsWordInDictionary(const wxString & strWord)212 bool HunspellInterface::IsWordInDictionary(const wxString& strWord)
213 {
214     if (m_pHunspell == NULL)
215         return false;
216 
217     wxCharBuffer wordCharBuffer = ConvertToUnicode(strWord);
218     if ( wordCharBuffer.data() == NULL )
219         return false;
220 
221     bool spelledOK = (m_pHunspell->spell(wordCharBuffer) == 1);
222     bool isInDict  = m_PersonalDictionary.IsWordInDictionary(strWord);
223 
224     return (spelledOK || isInDict);
225 }
226 
AddWordToDictionary(const wxString & strWord)227 int HunspellInterface::AddWordToDictionary(const wxString& strWord)
228 {
229     m_PersonalDictionary.AddWord(strWord);
230     m_bPersonalDictionaryModified = true;
231     return true;
232 }
233 
RemoveWordFromDictionary(const wxString & strWord)234 int HunspellInterface::RemoveWordFromDictionary(const wxString& strWord)
235 {
236     m_PersonalDictionary.RemoveWord(strWord);
237     m_bPersonalDictionaryModified = true;
238     return true;
239 }
240 
GetWordListAsArray()241 wxArrayString HunspellInterface::GetWordListAsArray()
242 {
243     return m_PersonalDictionary.GetWordListAsArray();
244 }
245 
246 // Since MySpell doesn't have a concept of a personal dictionary, we can create a file
247 // to hold new words and if spell check fails then we check this map before asking the user
248 // It's not the best (as it won't support the affix feature of MySpell), but it'll work
249 
PopulateDictionaryMap(StringToStringMap * pLookupMap,const wxString & strDictionaryPath)250 void HunspellInterface::PopulateDictionaryMap(StringToStringMap* pLookupMap, const wxString& strDictionaryPath)
251 {
252     if (pLookupMap == NULL)
253         pLookupMap = &m_DictionaryLookupMap;
254 
255     pLookupMap->clear();
256 
257     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Afrikaans (South Africa)"), _T("af_ZA"));
258     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Bulgarian (Bulgaria)"), _T("bg_BG"));
259     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Catalan (Spain)"), _T("ca_ES"));
260     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Czech (Czech Republic)"), _T("cs_CZ"));
261     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Welsh (Wales)"), _T("cy_GB"));
262     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Danish (Denmark)"), _T("da_DK"));
263     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("German (Austria)"), _T("de_AT"));
264     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("German (Switzerland)"), _T("de_CH"));
265     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("German (Germany-orig dict)"), _T("de_DE"));
266     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("German (Germany-old & neu ortho)"), _T("de_DE_comb"));
267     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("German (Germany-neu ortho)"), _T("de_DE_neu"));
268     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Greek (Greece)"), _T("el_GR"));
269     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("English (Australia)"), _T("en_AU"));
270     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("English (Canada)"), _T("en_CA"));
271     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("English (United Kingdom)"), _T("en_GB"));
272     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("English (New Zealand)"), _T("en_NZ"));
273     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("English (United States)"), _T("en_US"));
274     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Esperanto (anywhere)"), _T("eo_l3"));
275     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Spanish (Spain-etal)"), _T("es_ES"));
276     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Spanish (Mexico)"), _T("es_MX"));
277     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Faroese (Faroe Islands)"), _T("fo_FO"));
278     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("French (France)"), _T("fr_FR"));
279     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Irish (Ireland)"), _T("ga_IE"));
280     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Scottish Gaelic (Scotland)"), _T("gd_GB"));
281     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Galician (Spain)"), _T("gl_ES"));
282     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Hebrew (Israel)"), _T("he_IL"));
283     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Croatian (Croatia)"), _T("hr_HR"));
284     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Hungarian (Hungary)"), _T("hu_HU"));
285     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Interlingua (x-register)"), _T("ia"));
286     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Indonesian (Indonesia)"), _T("id_ID"));
287     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Italian (Italy)"), _T("it_IT"));
288     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Kurdish (Turkey)"), _T("ku_TR"));
289     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Latin (x-register)"), _T("la"));
290     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Lithuanian (Lithuania)"), _T("lt_LT"));
291     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Latvian (Latvia)"), _T("lv_LV"));
292     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Malagasy (Madagascar)"), _T("mg_MG"));
293     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Maori (New Zealand)"), _T("mi_NZ"));
294     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Malay (Malaysia)"), _T("ms_MY"));
295     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Norwegian Bokmaal (Norway)"), _T("nb_NO"));
296     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Dutch (Netherlands)"), _T("nl_NL"));
297     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Norwegian Nynorsk (Norway)"), _T("nn_NO"));
298     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Chichewa (Malawi)"), _T("ny_MW"));
299     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Polish (Poland)"), _T("pl_PL"));
300     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Portuguese (Brazil)"), _T("pt_BR"));
301     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Portuguese (Portugal)"), _T("pt_PT"));
302     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Romanian (Romania)"), _T("ro_RO"));
303     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Russian (Russia)"), _T("ru_RU"));
304     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Russian ye (Russia)"), _T("ru_RU_ie"));
305     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Russian yo (Russia)"), _T("ru_RU_yo"));
306     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Kinyarwanda (Rwanda)"), _T("rw_RW"));
307     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Slovak (Slovakia)"), _T("sk_SK"));
308     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Slovenian (Slovenia)"), _T("sl_SI"));
309     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Swedish (Sweden)"), _T("sv_SE"));
310     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Swahili (Kenya)"), _T("sw_KE"));
311     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Tetum (Indonesia)"), _T("tet_ID"));
312     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Tagalog (Philippines)"), _T("tl_PH"));
313     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Tswana (South Africa)"), _T("tn_ZA"));
314     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Ukrainian (Ukraine)"), _T("uk_UA"));
315     AddDictionaryElement(pLookupMap, strDictionaryPath, _T("Zulu (South Africa)"), _T("zu_ZA"));
316 
317     // Add the custom MySpell dictionary entries to the map
318     StringToStringMap::iterator start = m_CustomMySpellDictionaryMap.begin();
319     StringToStringMap::iterator stop  = m_CustomMySpellDictionaryMap.end();
320     while (start != stop)
321     {
322         AddDictionaryElement(pLookupMap, strDictionaryPath, (*start).first, (*start).second);
323         start++;
324     }
325 }
326 
UpdatePossibleValues(SpellCheckEngineOption & OptionDependency,SpellCheckEngineOption & OptionToUpdate)327 void HunspellInterface::UpdatePossibleValues(SpellCheckEngineOption& OptionDependency, SpellCheckEngineOption& OptionToUpdate)
328 {
329     if (   (OptionDependency.GetName().IsSameAs(_T("dictionary-path")))
330             && (OptionToUpdate.GetName().IsSameAs(_T("language"))) )
331     {
332         StringToStringMap tempLookupMap;
333         wxString strDictionaryPath = OptionDependency.GetValueAsString();
334         PopulateDictionaryMap(&tempLookupMap, strDictionaryPath);
335 
336         StringToStringMap::iterator start = tempLookupMap.begin();
337         StringToStringMap::iterator stop = tempLookupMap.end();
338         while (start != stop)
339         {
340             OptionToUpdate.AddPossibleValue((*start).first);
341             start++;
342         }
343     }
344     else
345     {
346         wxMessageOutput* msgOut = wxMessageOutput::Get();
347         if (msgOut)
348             msgOut->Printf(_("Unsure how to update the possible values for %s based on the value of %s"),
349                            OptionDependency.GetText().c_str(), OptionToUpdate.GetText().c_str());
350     }
351 }
352 
AddDictionaryElement(StringToStringMap * pLookupMap,const wxString & strDictionaryPath,const wxString & strDictionaryName,const wxString & strDictionaryFileRoot)353 void HunspellInterface::AddDictionaryElement(StringToStringMap* pLookupMap, const wxString& strDictionaryPath, const wxString& strDictionaryName, const wxString& strDictionaryFileRoot)
354 {
355     wxFileName strAffixFileName(strDictionaryPath + wxFILE_SEP_PATH + strDictionaryFileRoot + _T(".aff"));
356     wxFileName strDictionaryFileName(strDictionaryPath + wxFILE_SEP_PATH + strDictionaryFileRoot + _T(".dic"));
357 
358     if (strAffixFileName.FileExists() && strDictionaryFileName.FileExists())
359         (*pLookupMap)[strDictionaryName] = strDictionaryFileRoot;
360 }
361 
GetSelectedLanguage()362 wxString HunspellInterface::GetSelectedLanguage()
363 {
364     OptionsMap::iterator it = m_Options.find(_T("language"));
365     if (it != m_Options.end())
366         return it->second.GetValueAsString();
367 
368     return wxEmptyString;
369 }
370 
GetAffixFileName()371 wxString HunspellInterface::GetAffixFileName()
372 {
373     OptionsMap::iterator it = m_Options.find(_T("affix-file"));
374     if (it != m_Options.end())
375         return it->second.GetValueAsString();
376 
377     else
378     {
379         wxString strLanguage = GetSelectedLanguage();
380         if (strLanguage != wxEmptyString)
381             return GetAffixFileName(strLanguage);
382     }
383     return wxEmptyString;
384 }
385 
GetAffixFileName(const wxString & strDictionaryName)386 wxString HunspellInterface::GetAffixFileName(const wxString& strDictionaryName)
387 {
388     StringToStringMap::iterator finder = m_DictionaryLookupMap.find(strDictionaryName);
389     if (finder != m_DictionaryLookupMap.end())
390         return (m_strDictionaryPath + wxFILE_SEP_PATH + (*finder).second + _T(".aff"));
391 
392     return wxEmptyString;
393 }
394 
GetDictionaryFileName()395 wxString HunspellInterface::GetDictionaryFileName()
396 {
397     OptionsMap::iterator it = m_Options.find(_T("dict-file"));
398     if (it != m_Options.end())
399         return it->second.GetValueAsString();
400 
401     wxString strLanguage = GetSelectedLanguage();
402     if (strLanguage != wxEmptyString)
403         return GetDictionaryFileName(strLanguage);
404 
405     return wxEmptyString;
406 }
407 
GetDictionaryFileName(const wxString & strDictionaryName)408 wxString HunspellInterface::GetDictionaryFileName(const wxString& strDictionaryName)
409 {
410     StringToStringMap::iterator finder = m_DictionaryLookupMap.find(strDictionaryName);
411     if (finder != m_DictionaryLookupMap.end())
412         return (m_strDictionaryPath + wxFILE_SEP_PATH + (*finder).second + _T(".dic"));
413 
414     return wxEmptyString;
415 }
416 
AddCustomMySpellDictionary(const wxString & strDictionaryName,const wxString & strDictionaryFileRoot)417 void HunspellInterface::AddCustomMySpellDictionary(const wxString& strDictionaryName, const wxString& strDictionaryFileRoot)
418 {
419     m_CustomMySpellDictionaryMap[strDictionaryName] = strDictionaryFileRoot;
420 }
421 
OpenPersonalDictionary(const wxString & strPersonalDictionaryFile)422 void HunspellInterface::OpenPersonalDictionary(const wxString& strPersonalDictionaryFile)
423 {
424     m_PersonalDictionary.SetDictionaryFileName(strPersonalDictionaryFile);
425     m_PersonalDictionary.LoadPersonalDictionary();
426 }
427 
GetCharacterEncoding()428 wxString HunspellInterface::GetCharacterEncoding()
429 {
430     if (m_pHunspell == NULL)
431         return wxEmptyString;
432 
433     wxString encoding(wxConvUTF8.cMB2WC(m_pHunspell->get_dic_encoding()), *wxConvCurrent);
434     return encoding;
435 }
436 
437 ///////////// Options /////////////////
438 // "dictionary-path" - location of dictionary files
439 // "language" - selected language
440 //
441 // - OR -
442 //
443 // "dict-file" - dictionary file
444 // "affix-file" - affix file
445