From e5d57519f573ef3718ec207c6f81006b4a0e0244 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Wed, 11 May 2016 10:45:10 +0200
Subject: Improve Linux spell checking UX and enable it by default

This removes support for user dictionaries for now. The new
UI shows a list human readable languages (in their native
spelling) where the user can select one to use for spell
checking.

Updated our InstallSwiftDependencies.sh based on the package
names in their repositories.

Test-Information:

Tested on Ubuntu 16.04 with Hunspell and tested it still
builds on OS X 10.11.4.

Did not test InstallSwiftDependencies.sh.

Change-Id: I24fc705b1495f7c39a8da149cbd7116e41609998

diff --git a/BuildTools/InstallSwiftDependencies.sh b/BuildTools/InstallSwiftDependencies.sh
index 2c1570f..391efe2 100755
--- a/BuildTools/InstallSwiftDependencies.sh
+++ b/BuildTools/InstallSwiftDependencies.sh
@@ -10,20 +10,20 @@ then
     SYSTEM_DISTRO=$(lsb_release -i -s)
     if [ "$SYSTEM_DISTRO" == "Debian" ]
     then
-        sudo apt-get install pkg-config libssl-dev qt5-default libqt5x11extras5-dev libqt5webkit5-dev qtmultimedia5-dev qttools5-dev-tools libminiupnpc-dev libnatpmp-dev
+        sudo apt-get install pkg-config libssl-dev qt5-default libqt5x11extras5-dev libqt5webkit5-dev qtmultimedia5-dev qttools5-dev-tools libminiupnpc-dev libnatpmp-dev libhunspell-dev
     elif [ "$SYSTEM_DISTRO" == "Ubuntu" ]
     then
-        sudo apt-get install pkg-config libssl-dev qt5-default libqt5x11extras5-dev libqt5webkit5-dev qtmultimedia5-dev qttools5-dev-tools
+        sudo apt-get install pkg-config libssl-dev qt5-default libqt5x11extras5-dev libqt5webkit5-dev qtmultimedia5-dev qttools5-dev-tools libhunspell-dev
     elif [ "$SYSTEM_DISTRO" == "Arch" ]
     then
-        sudo pacman -S qt5-base qt5-x11extras qt5-webkit qt5-multimedia qt5-tools
+        sudo pacman -S qt5-base qt5-x11extras qt5-webkit qt5-multimedia qt5-tools hunspell
     elif [ "$SYSTEM_DISTRO" == "openSUSE project" ]
     then
-        sudo zypper in pkg-config libopenssl-devel libQt5Core-devel libQt5WebKit5-devel libQt5WebKitWidgets-devel libqt5-qtmultimedia-devel libqt5-qtx11extras-devel libqt5-qttools-devel libQt5Gui-devel libQt5Network-devel libQt5DBus-devel python-xml
+        sudo zypper in pkg-config libopenssl-devel libQt5Core-devel libQt5WebKit5-devel libQt5WebKitWidgets-devel libqt5-qtmultimedia-devel libqt5-qtx11extras-devel libqt5-qttools-devel libQt5Gui-devel libQt5Network-devel libQt5DBus-devel python-xml hunspell-devel
     elif [ "$SYSTEM_DISTRO" == "Fedora" ]
     then
         sudo dnf groups install "C Development Tools and Libraries"
-        sudo dnf install openssl-devel qt5-qtbase-devel qt5-linguist qt5-qtwebkit-devel qt5-qtmultimedia-devel qt5-qtx11extras-devel
+        sudo dnf install openssl-devel qt5-qtbase-devel qt5-linguist qt5-qtwebkit-devel qt5-qtmultimedia-devel qt5-qtx11extras-devel hunspell-devel
     else
         echo "Unsupported Linux distribution."
     fi
diff --git a/BuildTools/SCons/SConscript.boot b/BuildTools/SCons/SConscript.boot
index c335934..13a49b8 100644
--- a/BuildTools/SCons/SConscript.boot
+++ b/BuildTools/SCons/SConscript.boot
@@ -48,7 +48,7 @@ vars.Add("openssl_libnames", "Comma-separated openssl library names to override
 vars.Add("openssl_include", "Location of OpenSSL include files (if not under (openssl)/include)", None)
 vars.Add("openssl_libdir", "Location of OpenSSL library files (if not under (openssl)/lib)", None)
 vars.Add(PackageVariable("hunspell_prefix", "Hunspell location", False))
-vars.Add(BoolVariable("hunspell_enable", "Build with Hunspell support", False))
+vars.Add(BoolVariable("hunspell_enable", "Build with Hunspell support", True))
 vars.Add(PathVariable("boost_includedir", "Boost headers location", None, PathVariable.PathAccept))
 vars.Add(PathVariable("boost_libdir", "Boost library location", None, PathVariable.PathAccept))
 vars.Add(BoolVariable("boost_bundled_enable", "Allow use of bundled Boost as last resort", "true"))
diff --git a/SwifTools/HunspellChecker.cpp b/SwifTools/HunspellChecker.cpp
index fb1a5d6..1de369b 100644
--- a/SwifTools/HunspellChecker.cpp
+++ b/SwifTools/HunspellChecker.cpp
@@ -15,47 +15,151 @@
 #include <algorithm>
 
 #include <boost/algorithm/string.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/regex.hpp>
 
 #include <hunspell/hunspell.hxx>
 
+#include <Swiften/Base/Log.h>
+
 namespace Swift {
 
-HunspellChecker::HunspellChecker(const char* affix_path, const char* dictionary_path) {
-    speller_ = new Hunspell(affix_path, dictionary_path);
+static std::vector<std::string> recursiveFileSearch(const std::vector<std::string>& paths, const boost::regex& regex) {
+    std::vector<std::string> matches;
+
+    for (auto& path : paths) {
+        if (boost::filesystem::exists(path)) {
+            boost::cmatch what;
+            for (auto filename : boost::make_iterator_range(boost::filesystem::directory_iterator(path), boost::filesystem::directory_iterator())) {
+                if (boost::filesystem::is_regular_file(filename) && boost::regex_match(filename.path().c_str(), what, regex)) {
+                    matches.push_back(filename.path().string());
+                }
+            }
+        }
+    }
+    return matches;
+}
+
+HunspellChecker::HunspellChecker() {
 }
 
 HunspellChecker::~HunspellChecker() {
-    delete speller_;
+}
+
+std::vector<std::string> HunspellChecker::hunspellDictionaryPaths() const {
+    // The following list of paths comes from the source of the Hunspell command line tool.
+    std::vector<std::string> paths = {
+        "/usr/share/hunspell",
+        "/usr/share/myspell",
+        "/usr/share/myspell/dicts",
+        "/Library/Spelling",
+        "/opt/openoffice.org/basis3.0/share/dict/ooo",
+        "/usr/lib/openoffice.org/basis3.0/share/dict/ooo",
+        "/opt/openoffice.org2.4/share/dict/ooo",
+        "/usr/lib/openoffice.org2.4/share/dict/ooo",
+        "/opt/openoffice.org2.3/share/dict/ooo",
+        "/usr/lib/openoffice.org2.3/share/dict/ooo",
+        "/opt/openoffice.org2.2/share/dict/ooo",
+        "/usr/lib/openoffice.org2.2/share/dict/ooo",
+        "/opt/openoffice.org2.1/share/dict/ooo",
+        "/usr/lib/openoffice.org2.1/share/dict/ooo",
+        "/opt/openoffice.org2.0/share/dict/ooo",
+        "/usr/lib/openoffice.org2.0/share/dict/ooo"
+    };
+
+    if (std::getenv("DICPATH")) {
+        std::string dicpathEnvironment(std::getenv("DICPATH"));
+        std::vector<std::string> dicpaths;
+        boost::split(dicpaths,dicpathEnvironment,boost::is_any_of(":"));
+        paths.insert(paths.begin(), dicpaths.begin(), dicpaths.end());
+    }
+
+    return paths;
+}
+
+bool HunspellChecker::isAutomaticallyDetectingLanguage() {
+    return false;
+}
+
+void HunspellChecker::setActiveLanguage(const std::string& language) {
+    auto dictionaries = detectedDictionaries();
+    if (dictionaries.find(language) != dictionaries.end()) {
+        SWIFT_LOG(debug) << "Initialized Hunspell with dic,aff files " << dictionaries[language].dicPath << " , " << dictionaries[language].affPath << std::endl;
+        speller_ = std::unique_ptr<Hunspell>(new Hunspell(dictionaries[language].affPath.c_str(), dictionaries[language].dicPath.c_str()));
+        activeLangauge_ = language;
+    }
+    else {
+        SWIFT_LOG(warning) << "Unsupported language '" << language << "'" << std::endl;
+    }
+}
+
+std::string HunspellChecker::activeLanguage() const {
+    return activeLangauge_.get_value_or("");
+}
+
+std::vector<std::string> HunspellChecker::supportedLanguages() const {
+    std::vector<std::string> languages;
+
+    for (const auto& n : detectedDictionaries()) {
+        languages.push_back(n.first);
+    }
+
+    return languages;
+}
+
+std::unordered_map<std::string, HunspellChecker::Dictionary> HunspellChecker::detectedDictionaries() const {
+    std::unordered_map<std::string, HunspellChecker::Dictionary> dictionaries;
+
+    auto dictionaryFiles = recursiveFileSearch(hunspellDictionaryPaths(), boost::regex(".*\\.dic$"));
+    for (const auto& dictionary : dictionaryFiles) {
+        std::string correspondingAffixPath = dictionary;
+        boost::replace_last(correspondingAffixPath, ".dic", ".aff");
+        if (boost::filesystem::is_regular_file(correspondingAffixPath)) {
+            auto filenameWithoutExtension = boost::filesystem::basename(dictionary);
+            dictionaries[filenameWithoutExtension] = {dictionary, correspondingAffixPath};
+        }
+    }
+
+    return dictionaries;
 }
 
 bool HunspellChecker::isCorrect(const std::string& word) {
-    return speller_->spell(word.c_str());
+    if (speller_) {
+        return speller_->spell(word.c_str());
+    }
+    else {
+        return true;
+    }
 }
 
 void HunspellChecker::getSuggestions(const std::string& word, std::vector<std::string>& list) {
-    char **suggestList = NULL;
-    int words_returned = 0;
-    if (!word.empty()) {
-        words_returned = speller_->suggest(&suggestList, word.c_str());
-        if (suggestList != NULL) {
-            for (int i = 0; i < words_returned; ++i) {
-                list.push_back(suggestList[i]);
-                free(suggestList[i]);
+    if (speller_) {
+        char **suggestList = NULL;
+        int words_returned = 0;
+        if (!word.empty()) {
+            words_returned = speller_->suggest(&suggestList, word.c_str());
+            if (suggestList != NULL) {
+                for (int i = 0; i < words_returned; ++i) {
+                    list.push_back(suggestList[i]);
+                    free(suggestList[i]);
+                }
+                free(suggestList);
             }
-            free(suggestList);
         }
     }
 }
 
 void HunspellChecker::checkFragment(const std::string& fragment, PositionPairList& misspelledPositions) {
-    if (!fragment.empty()) {
-        parser_->check(fragment, misspelledPositions);
-        for (PositionPairList::iterator it = misspelledPositions.begin(); it != misspelledPositions.end();) {
-            if (isCorrect(fragment.substr(boost::get<0>(*it), boost::get<1>(*it) - boost::get<0>(*it)))) {
-                it = misspelledPositions.erase(it);
-            }
-            else {
-                ++it;
+    if (speller_) {
+        if (!fragment.empty()) {
+            parser_.check(fragment, misspelledPositions);
+            for (PositionPairList::iterator it = misspelledPositions.begin(); it != misspelledPositions.end();) {
+                if (isCorrect(fragment.substr(boost::get<0>(*it), boost::get<1>(*it) - boost::get<0>(*it)))) {
+                    it = misspelledPositions.erase(it);
+                }
+                else {
+                    ++it;
+                }
             }
         }
     }
diff --git a/SwifTools/HunspellChecker.h b/SwifTools/HunspellChecker.h
index 076b468..2d4831e 100644
--- a/SwifTools/HunspellChecker.h
+++ b/SwifTools/HunspellChecker.h
@@ -12,10 +12,13 @@
 
 #pragma once
 
+#include <memory>
+#include <string>
+#include <unordered_map>
 #include <vector>
 
 #include <boost/algorithm/string.hpp>
-#include <boost/tuple/tuple.hpp>
+#include <boost/optional.hpp>
 
 #include <SwifTools/SpellChecker.h>
 
@@ -24,12 +27,31 @@ class Hunspell;
 namespace Swift {
     class HunspellChecker : public SpellChecker {
         public:
-            HunspellChecker(const char* affix_path, const char* dict_path);
+            HunspellChecker();
             virtual ~HunspellChecker();
+
+            virtual bool isAutomaticallyDetectingLanguage();
+
+            virtual void setActiveLanguage(const std::string& language);
+            virtual std::string activeLanguage() const;
+            virtual std::vector<std::string> supportedLanguages() const;
+
             virtual bool isCorrect(const std::string& word);
             virtual void getSuggestions(const std::string& word, std::vector<std::string>& list);
             virtual void checkFragment(const std::string& fragment, PositionPairList& misspelledPositions);
+
         private:
-            Hunspell* speller_;
+            struct Dictionary {
+                std::string dicPath;
+                std::string affPath;
+            };
+
+            std::unordered_map<std::string, Dictionary> detectedDictionaries() const;
+            std::vector<std::string> hunspellDictionaryPaths() const;
+
+        private:
+            std::unique_ptr<Hunspell> speller_;
+            boost::optional<std::string> activeLangauge_;
+
     };
 }
diff --git a/SwifTools/MacOSXChecker.h b/SwifTools/MacOSXChecker.h
index be9a32a..7587c99 100644
--- a/SwifTools/MacOSXChecker.h
+++ b/SwifTools/MacOSXChecker.h
@@ -23,6 +23,13 @@ namespace Swift {
         public:
             MacOSXChecker();
             virtual ~MacOSXChecker();
+
+            virtual bool isAutomaticallyDetectingLanguage();
+
+            virtual void setActiveLanguage(const std::string& language);
+            virtual std::string activeLanguage() const;
+            virtual std::vector<std::string> supportedLanguages() const;
+
             virtual bool isCorrect(const std::string& word);
             virtual void getSuggestions(const std::string& word, std::vector<std::string>& list);
             virtual void checkFragment(const std::string& fragment, PositionPairList& misspelledPositions);
diff --git a/SwifTools/MacOSXChecker.mm b/SwifTools/MacOSXChecker.mm
index 5f4f9c3..519f06c 100644
--- a/SwifTools/MacOSXChecker.mm
+++ b/SwifTools/MacOSXChecker.mm
@@ -13,6 +13,7 @@
 #include <SwifTools/MacOSXChecker.h>
 
 #include <algorithm>
+#include <cassert>
 
 #include <boost/algorithm/string.hpp>
 
@@ -33,6 +34,21 @@ bool MacOSXChecker::isCorrect(const std::string& /*word*/) {
     return false;
 }
 
+bool MacOSXChecker::isAutomaticallyDetectingLanguage() {
+    return true;
+}
+
+void MacOSXChecker::setActiveLanguage(const std::string& /*language*/) {
+    assert(false);
+}
+
+std::string MacOSXChecker::activeLanguage() const {
+    assert(false);
+}
+std::vector<std::string> MacOSXChecker::supportedLanguages() const {
+    assert(false);
+}
+
 void MacOSXChecker::getSuggestions(const std::string& word, std::vector<std::string>& list) {
     NSSpellChecker* spellChecker = [NSSpellChecker sharedSpellChecker];
     NSString* wordString = [[NSString alloc] initWithUTF8String: word.c_str()];
diff --git a/SwifTools/SpellChecker.h b/SwifTools/SpellChecker.h
index 415d3f6..664fc63 100644
--- a/SwifTools/SpellChecker.h
+++ b/SwifTools/SpellChecker.h
@@ -14,24 +14,28 @@
 
 #include <vector>
 
-#include <boost/algorithm/string.hpp>
-#include <boost/tuple/tuple.hpp>
-
 #include <SwifTools/SpellParser.h>
 
 namespace Swift {
     class SpellChecker {
         public:
             SpellChecker() {
-                parser_ = new SpellParser();
             }
+
             virtual ~SpellChecker() {
-                delete parser_;
             }
+
+            virtual bool isAutomaticallyDetectingLanguage() = 0;
+
+            virtual void setActiveLanguage(const std::string& language) = 0;
+            virtual std::string activeLanguage() const = 0;
+            virtual std::vector<std::string> supportedLanguages() const = 0;
+
             virtual bool isCorrect(const std::string& word) = 0;
             virtual void getSuggestions(const std::string& word, std::vector<std::string>& list) = 0;
             virtual void checkFragment(const std::string& fragment, PositionPairList& misspelledPositions) = 0;
+
         protected:
-            SpellParser *parser_;
+            SpellParser parser_;
     };
 }
diff --git a/SwifTools/SpellCheckerFactory.cpp b/SwifTools/SpellCheckerFactory.cpp
index e53447e..bfd3d4a 100644
--- a/SwifTools/SpellCheckerFactory.cpp
+++ b/SwifTools/SpellCheckerFactory.cpp
@@ -4,6 +4,12 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
+/*
+ * Copyright (c) 2016 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
 #include <boost/filesystem/operations.hpp>
 
 #include <SwifTools/SpellChecker.h>
@@ -23,17 +29,11 @@ SpellCheckerFactory::SpellCheckerFactory() {
 }
 
 #ifdef HAVE_HUNSPELL
-SpellChecker* SpellCheckerFactory::createSpellChecker(const std::string& dictFile) {
-    std::string affixFile(dictFile);
-    boost::replace_all(affixFile, ".dic", ".aff");
-    if ((boost::filesystem::exists(dictFile)) && (boost::filesystem::exists(affixFile))) {
-        return new HunspellChecker(affixFile.c_str(), dictFile.c_str());
-    }
-    // If dictionaries don't exist disable the checker
-    return NULL;
+SpellChecker* SpellCheckerFactory::createSpellChecker() {
+    return new HunspellChecker();
 }
 #elif defined(SWIFTEN_PLATFORM_MACOSX)
-SpellChecker* SpellCheckerFactory::createSpellChecker(const std::string& /*dictFile*/) {
+SpellChecker* SpellCheckerFactory::createSpellChecker() {
     return new MacOSXChecker();
 }
 #endif
diff --git a/SwifTools/SpellCheckerFactory.h b/SwifTools/SpellCheckerFactory.h
index 2e1711a..eb2ade6 100644
--- a/SwifTools/SpellCheckerFactory.h
+++ b/SwifTools/SpellCheckerFactory.h
@@ -28,6 +28,6 @@ namespace Swift {
     class SpellCheckerFactory {
         public:
             SpellCheckerFactory();
-            SpellChecker* createSpellChecker(const std::string& dictFile);
+            SpellChecker* createSpellChecker();
     };
 }
diff --git a/Swift/Controllers/SettingConstants.cpp b/Swift/Controllers/SettingConstants.cpp
index 9807abc..d9766cf 100644
--- a/Swift/Controllers/SettingConstants.cpp
+++ b/Swift/Controllers/SettingConstants.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012-2015 Isode Limited.
+ * Copyright (c) 2012-2016 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -21,9 +21,7 @@ const SettingsProvider::Setting<std::string> SettingConstants::EXPANDED_ROSTER_G
 const SettingsProvider::Setting<bool> SettingConstants::PLAY_SOUNDS("playSounds", true);
 const SettingsProvider::Setting<std::string> SettingConstants::HIGHLIGHT_RULES("highlightRules", "@");
 const SettingsProvider::Setting<bool> SettingConstants::SPELL_CHECKER("spellChecker", false);
-const SettingsProvider::Setting<std::string> SettingConstants::DICT_PATH("dictPath", "/usr/share/myspell/dicts/");
-const SettingsProvider::Setting<std::string> SettingConstants::PERSONAL_DICT_PATH("personaldictPath", "/home/");
-const SettingsProvider::Setting<std::string> SettingConstants::DICT_FILE("dictFile", "en_US.dic");
+const SettingsProvider::Setting<std::string> SettingConstants::SPELL_CHECKER_LANGUAGE("spellCheckerLanguage", "en_US");
 const SettingsProvider::Setting<std::string> SettingConstants::INVITE_AUTO_ACCEPT_MODE("inviteAutoAcceptMode", "presence");
 const SettingsProvider::Setting<std::string> SettingConstants::TRELLIS_GRID_SIZE("trellisGridSize", "");
 const SettingsProvider::Setting<std::string> SettingConstants::TRELLIS_GRID_POSITIONS("trellisGridPositions", "");
diff --git a/Swift/Controllers/SettingConstants.h b/Swift/Controllers/SettingConstants.h
index 9343b7b..ace481b 100644
--- a/Swift/Controllers/SettingConstants.h
+++ b/Swift/Controllers/SettingConstants.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012-2015 Isode Limited.
+ * Copyright (c) 2012-2016 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -58,9 +58,7 @@ namespace Swift {
              */
             static const SettingsProvider::Setting<std::string> HIGHLIGHT_RULES;
             static const SettingsProvider::Setting<bool> SPELL_CHECKER;
-            static const SettingsProvider::Setting<std::string> DICT_PATH;
-            static const SettingsProvider::Setting<std::string> PERSONAL_DICT_PATH;
-            static const SettingsProvider::Setting<std::string> DICT_FILE;
+            static const SettingsProvider::Setting<std::string> SPELL_CHECKER_LANGUAGE;
             /**
              * The #INVITE_AUTO_ACCEPT_MODE setting specifies how to handle invites to chat rooms.
              *
diff --git a/Swift/QtUI/QtSpellCheckerWindow.cpp b/Swift/QtUI/QtSpellCheckerWindow.cpp
index 9232b19..ed7e7e8 100644
--- a/Swift/QtUI/QtSpellCheckerWindow.cpp
+++ b/Swift/QtUI/QtSpellCheckerWindow.cpp
@@ -18,6 +18,8 @@
 #include <QStringList>
 #include <QTimer>
 
+#include <Swiften/Base/Log.h>
+
 #include <Swift/Controllers/SettingConstants.h>
 #include <Swift/Controllers/Settings/SettingsProvider.h>
 
@@ -38,7 +40,6 @@ QtSpellCheckerWindow::QtSpellCheckerWindow(SettingsProvider* settings, QWidget*
     connect(ui_.spellChecker, SIGNAL(toggled(bool)), this, SLOT(handleChecker(bool)));
     connect(ui_.cancel, SIGNAL(clicked()), this, SLOT(handleCancel()));
     connect(ui_.apply, SIGNAL(clicked()), this, SLOT(handleApply()));
-    connect(ui_.pathButton, SIGNAL(clicked()), this, SLOT(handlePathButton()));
     setFromSettings();
 }
 
@@ -48,27 +49,39 @@ void QtSpellCheckerWindow::shrinkWindow() {
 
 void QtSpellCheckerWindow::setFromSettings() {
     ui_.spellChecker->setChecked(settings_->getSetting(SettingConstants::SPELL_CHECKER));
-    ui_.pathContent->setText(P2QSTRING(settings_->getSetting(SettingConstants::DICT_PATH)));
-    ui_.currentLanguageValue->setText(P2QSTRING(settings_->getSetting(SettingConstants::DICT_FILE)));
-    std::string currentPath = settings_->getSetting(SettingConstants::DICT_PATH);
-    QString filename = "*.dic";
-    QDir dictDirectory = QDir(P2QSTRING(currentPath));
-    QStringList files = dictDirectory.entryList(QStringList(filename), QDir::Files);
-    showFiles(files);
     setEnabled(settings_->getSetting(SettingConstants::SPELL_CHECKER));
 }
 
+void QtSpellCheckerWindow::setSupportedLanguages(const std::vector<std::string>& languages) {
+    languageItems_.clear();
+    ui_.languageView->clear();
+    for (const auto& shortLang : languages) {
+        auto locale = QLocale(P2QSTRING(shortLang));
+        auto label = QString("%1 ( %2 )").arg(locale.nativeLanguageName(), locale.nativeCountryName());
+
+        QListWidgetItem* item = new QListWidgetItem(label);
+        item->setData(Qt::UserRole, P2QSTRING(shortLang));
+        languageItems_[shortLang] = item;
+        ui_.languageView->addItem(item);
+    }
+}
+
+void QtSpellCheckerWindow::setActiveLanguage(const std::string& language) {
+    SWIFT_LOG_ASSERT(languageItems_.find(language) != languageItems_.end(), warning) << "Language '" << language << "' is not available." << std::endl;
+    if (languageItems_.find(language) != languageItems_.end()) {
+        languageItems_[language]->setSelected(true);
+    }
+}
+
+void QtSpellCheckerWindow::setAutomaticallyIdentifiesLanguage(bool isAutomaticallyIdentifying) {
+    ui_.languageView->setHidden(isAutomaticallyIdentifying);
+}
+
 void QtSpellCheckerWindow::handleChecker(bool state) {
     setEnabled(state);
 }
 
 void QtSpellCheckerWindow::setEnabled(bool state) {
-    ui_.pathContent->setEnabled(state);
-    ui_.languageView->setEnabled(state);
-    ui_.pathButton->setEnabled(state);
-    ui_.pathLabel->setEnabled(state);
-    ui_.currentLanguage->setEnabled(state);
-    ui_.currentLanguageValue->setEnabled(state);
     ui_.language->setEnabled(state);
 }
 
@@ -76,7 +89,7 @@ void QtSpellCheckerWindow::handleApply() {
     settings_->storeSetting(SettingConstants::SPELL_CHECKER, ui_.spellChecker->isChecked());
     QList<QListWidgetItem* > selectedLanguage = ui_.languageView->selectedItems();
     if (!selectedLanguage.empty()) {
-        settings_->storeSetting(SettingConstants::DICT_FILE, Q2PSTRING((selectedLanguage.first())->text()));
+        settings_->storeSetting(SettingConstants::SPELL_CHECKER_LANGUAGE, Q2PSTRING(selectedLanguage.first()->data(Qt::UserRole).toString()));
     }
     this->done(0);
 }
@@ -85,38 +98,4 @@ void QtSpellCheckerWindow::handleCancel() {
     this->done(0);
 }
 
-void QtSpellCheckerWindow::handlePathButton() {
-    std::string currentPath = settings_->getSetting(SettingConstants::DICT_PATH);
-    QString dirpath = QFileDialog::getExistingDirectory(this, tr("Dictionary Path"), P2QSTRING(currentPath));
-    if (dirpath != P2QSTRING(currentPath)) {
-        ui_.languageView->clear();
-        settings_->storeSetting(SettingConstants::DICT_FILE, "");
-        ui_.currentLanguageValue->setText(" ");
-    }
-    if (!dirpath.isEmpty()) {
-        if (!dirpath.endsWith("/")) {
-            dirpath.append("/");
-        }
-        settings_->storeSetting(SettingConstants::DICT_PATH, Q2PSTRING(dirpath));
-        QDir dictDirectory = QDir(dirpath);
-        ui_.pathContent->setText(dirpath);
-        QString filename = "*.dic";
-        QStringList files = dictDirectory.entryList(QStringList(filename), QDir::Files);
-        showFiles(files);
-    }
-}
-
-void QtSpellCheckerWindow::handlePersonalPathButton() {
-    std::string currentPath = settings_->getSetting(SettingConstants::PERSONAL_DICT_PATH);
-    QString filename = QFileDialog::getOpenFileName(this, tr("Select Personal Dictionary"), P2QSTRING(currentPath), tr("(*.dic"));
-    settings_->storeSetting(SettingConstants::PERSONAL_DICT_PATH, Q2PSTRING(filename));
-}
-
-void QtSpellCheckerWindow::showFiles(const QStringList& files) {
-    ui_.languageView->clear();
-    for (int i = 0; i < files.size(); ++i) {
-        ui_.languageView->insertItem(i, files[i]);
-    }
-}
-
 }
diff --git a/Swift/QtUI/QtSpellCheckerWindow.h b/Swift/QtUI/QtSpellCheckerWindow.h
index 846dcbb..86ebd40 100644
--- a/Swift/QtUI/QtSpellCheckerWindow.h
+++ b/Swift/QtUI/QtSpellCheckerWindow.h
@@ -12,29 +12,41 @@
 
 #pragma once
 
+#include <string>
+#include <unordered_map>
+#include <vector>
+
 #include <QDialog>
 
 #include <Swift/QtUI/ui_QtSpellCheckerWindow.h>
 
+class QListWidgetItem;
+
 namespace Swift {
     class SettingsProvider;
     class QtSpellCheckerWindow : public QDialog, protected Ui::QtSpellCheckerWindow {
         Q_OBJECT
         public:
             QtSpellCheckerWindow(SettingsProvider* settings, QWidget* parent = nullptr);
+
+            void setSupportedLanguages(const std::vector<std::string>& languages);
+            void setActiveLanguage(const std::string& language);
+            void setAutomaticallyIdentifiesLanguage(bool isAutomaticallyIdentifying);
+
         public slots:
             void handleChecker(bool state);
             void handleCancel();
-            void handlePathButton();
-            void handlePersonalPathButton();
             void handleApply();
+
         private slots:
             void shrinkWindow();
+
         private:
             void setEnabled(bool state);
             void setFromSettings();
-            void showFiles(const QStringList& files);
+
             SettingsProvider* settings_;
             Ui::QtSpellCheckerWindow ui_;
+            std::unordered_map<std::string, QListWidgetItem*> languageItems_;
     };
 }
diff --git a/Swift/QtUI/QtSpellCheckerWindow.ui b/Swift/QtUI/QtSpellCheckerWindow.ui
index b7f5161..dcb70fa 100644
--- a/Swift/QtUI/QtSpellCheckerWindow.ui
+++ b/Swift/QtUI/QtSpellCheckerWindow.ui
@@ -25,45 +25,6 @@
     <widget class="QWidget" name="hunspellOptions" native="true">
      <layout class="QVBoxLayout" name="verticalLayout_2">
       <item>
-       <layout class="QHBoxLayout" name="horizontalLayout_2">
-        <item>
-         <widget class="QLabel" name="pathLabel">
-          <property name="text">
-           <string>Dictionary Path:</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QLineEdit" name="pathContent"/>
-        </item>
-        <item>
-         <widget class="QPushButton" name="pathButton">
-          <property name="text">
-           <string>Change</string>
-          </property>
-         </widget>
-        </item>
-       </layout>
-      </item>
-      <item>
-       <layout class="QHBoxLayout" name="horizontalLayout_3">
-        <item>
-         <widget class="QLabel" name="currentLanguage">
-          <property name="text">
-           <string>Current Language:</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QLabel" name="currentLanguageValue">
-          <property name="text">
-           <string/>
-          </property>
-         </widget>
-        </item>
-       </layout>
-      </item>
-      <item>
        <layout class="QHBoxLayout" name="horizontalLayout_4">
         <item>
          <widget class="QLabel" name="language">
@@ -73,7 +34,11 @@
          </widget>
         </item>
         <item>
-         <widget class="QListWidget" name="languageView"/>
+         <widget class="QListWidget" name="languageView">
+          <property name="alternatingRowColors">
+           <bool>true</bool>
+          </property>
+         </widget>
         </item>
        </layout>
       </item>
diff --git a/Swift/QtUI/QtTextEdit.cpp b/Swift/QtUI/QtTextEdit.cpp
index 846dcbc..3143192 100644
--- a/Swift/QtUI/QtTextEdit.cpp
+++ b/Swift/QtUI/QtTextEdit.cpp
@@ -179,17 +179,17 @@ void QtTextEdit::addSuggestions(QMenu* menu, QContextMenuEvent* event)
 
 
 #ifdef HAVE_SPELLCHECKER
-void QtTextEdit::setUpSpellChecker()
-{
+void QtTextEdit::setUpSpellChecker() {
     delete highlighter_;
     highlighter_ = nullptr;
     delete checker_;
     checker_ = nullptr;
     if (settings_->getSetting(SettingConstants::SPELL_CHECKER)) {
-        std::string dictPath = settings_->getSetting(SettingConstants::DICT_PATH);
-        std::string dictFile = settings_->getSetting(SettingConstants::DICT_FILE);
-        checker_ = SpellCheckerFactory().createSpellChecker(dictPath + dictFile);
+        checker_ = SpellCheckerFactory().createSpellChecker();
         if (checker_) {
+            if (!checker_->isAutomaticallyDetectingLanguage()) {
+                checker_->setActiveLanguage(settings_->getSetting(SettingConstants::SPELL_CHECKER_LANGUAGE));
+            }
             highlighter_ = new QtSpellCheckHighlighter(document(), checker_);
         }
         else {
@@ -213,12 +213,18 @@ void QtTextEdit::spellCheckerSettingsWindow() {
         spellCheckerWindow_->raise();
         spellCheckerWindow_->activateWindow();
     }
+    if (checker_) {
+        spellCheckerWindow_->setAutomaticallyIdentifiesLanguage(checker_->isAutomaticallyDetectingLanguage());
+        if (!checker_->isAutomaticallyDetectingLanguage()) {
+            spellCheckerWindow_->setSupportedLanguages(checker_->supportedLanguages());
+            spellCheckerWindow_->setActiveLanguage(checker_->activeLanguage());
+        }
+    }
 }
 
 void QtTextEdit::handleSettingChanged(const std::string& settings) {
-    if (settings == SettingConstants::SPELL_CHECKER.getKey()
-        || settings == SettingConstants::DICT_PATH.getKey()
-        || settings == SettingConstants::DICT_FILE.getKey()) {
+    if (settings == SettingConstants::SPELL_CHECKER.getKey() ||
+        settings == SettingConstants::SPELL_CHECKER_LANGUAGE.getKey()) {
 #ifdef HAVE_SPELLCHECKER
         setUpSpellChecker();
         if (highlighter_) {
-- 
cgit v0.10.2-6-g49f6