aboutsummaryrefslogtreecommitdiff
path: root/net-im/psi
diff options
context:
space:
mode:
authorAlexey Dokuchaev <danfe@FreeBSD.org>2019-04-07 14:45:50 +0000
committerAlexey Dokuchaev <danfe@FreeBSD.org>2019-04-07 14:45:50 +0000
commit59494c0dce2dcd9e8c41b6fcc9a47e6bf2940abd (patch)
tree5cd71b1da5008092a4a0623cca306c0a58e713b5 /net-im/psi
parent751dfb9495b6a6587fc2cf6e9bf8376b1b60aad4 (diff)
downloadports-59494c0dce2dcd9e8c41b6fcc9a47e6bf2940abd.tar.gz
ports-59494c0dce2dcd9e8c41b6fcc9a47e6bf2940abd.zip
- Update `net-im/psi' to version 1.4
- Replace current 2015 Hunspell implementation (by Sergey Ilinykh and Vitaly Tonkacheyev) with 2009 one by Alexander Tsvyashchenko, which turned out to be better alternative: * Much faster (suggestions appear almost instantly vs. several hundreds milliseconds with the original implementation) * Better multilanguage support (tested with English and Russian) * Ability to limit number of suggestions in the settings dialog * Working "add word to the dictionary" feature Obtained from: http://endl.ch/content/psi-spell-checking-hunspell-support
Notes
Notes: svn path=/head/; revision=498280
Diffstat (limited to 'net-im/psi')
-rw-r--r--net-im/psi/Makefile6
-rw-r--r--net-im/psi/distinfo6
-rw-r--r--net-im/psi/files/hunspellchecker.cpp215
-rw-r--r--net-im/psi/files/hunspellchecker.h71
-rw-r--r--net-im/psi/files/patch-git_4b838c015
-rw-r--r--net-im/psi/files/patch-src_libpsi_tools_spellchecker_spellchecker.cpp37
-rw-r--r--net-im/psi/files/patch-src_libpsi_tools_spellchecker_spellchecker.h19
-rw-r--r--net-im/psi/files/patch-src_msgmle.cpp116
-rw-r--r--net-im/psi/files/patch-src_msgmle.h28
-rw-r--r--net-im/psi/files/patch-src_options_opt__advanced.cpp66
-rw-r--r--net-im/psi/files/patch-src_options_opt__advanced.ui29
11 files changed, 588 insertions, 20 deletions
diff --git a/net-im/psi/Makefile b/net-im/psi/Makefile
index 727745a3bcc9..4866640ec735 100644
--- a/net-im/psi/Makefile
+++ b/net-im/psi/Makefile
@@ -2,8 +2,7 @@
# $FreeBSD$
PORTNAME= psi
-DISTVERSION= 1.3
-PORTREVISION= 4
+PORTVERSION= 1.4
CATEGORIES= net-im
MASTER_SITES= SF/${PORTNAME}/Psi/${PORTVERSION}
@@ -43,6 +42,9 @@ ENCHANT_LIB_DEPENDS= libenchant.so:textproc/enchant
ENCHANT_CMAKE_BOOL= USE_ENCHANT
post-patch:
+# Replace original Hunspell implementation with better alternative
+ @${CP} ${FILESDIR}/hunspellchecker.* \
+ ${WRKSRC}/src/libpsi/tools/spellchecker
# Avoid conflict with C++20 <version> by adding .txt suffix
@${MV} ${WRKSRC}/version ${WRKSRC}/version.txt
@${REINPLACE_CMD} -i .c++20 's,SOURCE_DIR}/version,&.txt,' \
diff --git a/net-im/psi/distinfo b/net-im/psi/distinfo
index fe4e129d9b9d..c4956a95e707 100644
--- a/net-im/psi/distinfo
+++ b/net-im/psi/distinfo
@@ -1,3 +1,3 @@
-TIMESTAMP = 1508773121
-SHA256 (psi-1.3.tar.xz) = 59debd16e61ab1d4ff88aca9f41b9caaaca8395f1576418fb99214d5e2c6fa8b
-SIZE (psi-1.3.tar.xz) = 2143076
+TIMESTAMP = 1541113245
+SHA256 (psi-1.4.tar.xz) = 761934c1c62daf69215f085ba24d7f9cd4db05ef0ad735383d68fb03d21571ad
+SIZE (psi-1.4.tar.xz) = 2119840
diff --git a/net-im/psi/files/hunspellchecker.cpp b/net-im/psi/files/hunspellchecker.cpp
new file mode 100644
index 000000000000..ed8eea8ef1cf
--- /dev/null
+++ b/net-im/psi/files/hunspellchecker.cpp
@@ -0,0 +1,215 @@
+/*
+ * hunspellchecker.cpp
+ *
+ * Copyright (C) 2009 Alexander Tsvyashchenko
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <QDir>
+#include <QCoreApplication>
+#include <QtDebug>
+#include <QTextCodec>
+
+#include <hunspell/hunspell.hxx>
+#include "hunspellchecker.h"
+
+HunSpellChecker::HunSpellChecker()
+{
+ QStringList dict_paths(getDictSearchPaths());
+
+ for (QStringList::const_iterator dict_path_it = dict_paths.begin(); dict_path_it != dict_paths.end(); ++dict_path_it) {
+ // Get all affixes present in given path.
+ QStringList affixes = QDir(*dict_path_it).entryList(QStringList("*.aff"), QDir::Files);
+
+ for (QStringList::const_iterator affix_it = affixes.begin(); affix_it != affixes.end(); ++affix_it) {
+ QString base_name(QFileInfo(*affix_it).baseName());
+ int sep_pos = base_name.indexOf("_");
+ QString lang = sep_pos != -1 ? base_name.left(sep_pos) : base_name;
+
+ if (!all_langs_.contains(lang))
+ all_langs_.append(lang);
+ }
+ }
+}
+
+void HunSpellChecker::clearSpellers()
+{
+ for (HunSpellers::const_iterator it = spellers_.begin(); it != spellers_.end(); ++it)
+ delete it.value().speller;
+
+ spellers_.clear();
+}
+
+HunSpellChecker::~HunSpellChecker()
+{
+ clearSpellers();
+}
+
+bool HunSpellChecker::isCorrect(const QString& word)
+{
+ if (!spellers_.empty()) {
+ for (HunSpellers::const_iterator it = spellers_.begin(); it != spellers_.end(); ++it) {
+ QByteArray word_enc = it.value().codec -> fromUnicode(word);
+ if (it.value().speller -> spell(word_enc.constData()) != 0)
+ return true;
+ }
+ return false;
+ }
+ return true;
+}
+
+QList<QString> HunSpellChecker::suggestions(const QString& word, const QString& lang, unsigned max_sugs)
+{
+ QList<QString> words;
+
+ HunSpellers::const_iterator it = spellers_.find(lang);
+
+ if (it != spellers_.end()) {
+ char** suggestions;
+ QByteArray word_enc = it.value().codec -> fromUnicode(word);
+
+ if (int sugs_num = it.value().speller -> suggest(&suggestions, word_enc.constData())) {
+ int sugs_out = max_sugs ? std::min((int)max_sugs, sugs_num) : sugs_num;
+ for (int i = 0; i < sugs_out; ++i) {
+ words << it.value().codec -> toUnicode(suggestions[i]);
+ }
+ it.value().speller -> free_list(&suggestions, sugs_num);
+ }
+ }
+
+ return words;
+}
+
+// FIXME: hunspell keeps added words in memory only!
+// To survive program exit they have to be saved/restored manually,
+// which is not done here.
+bool HunSpellChecker::add(const QString& word, const QString& lang)
+{
+ QString trimmed_word = word.trimmed();
+ HunSpellers::const_iterator it = spellers_.find(lang);
+
+ if(!trimmed_word.isEmpty() && it != spellers_.end()) {
+ QByteArray word_enc = it.value().codec -> fromUnicode(trimmed_word);
+ it.value().speller -> add(word_enc.constData());
+ return true;
+ }
+
+ return false;
+}
+
+bool HunSpellChecker::available() const
+{
+ return true;
+}
+
+bool HunSpellChecker::writable() const
+{
+ return true;
+}
+
+QList<QString> HunSpellChecker::getAllLanguages() const
+{
+ return all_langs_;
+}
+
+QList<QString> HunSpellChecker::getDictSearchPaths() const
+{
+ QStringList dict_paths(QString("%1/hunspell").arg(QCoreApplication::applicationDirPath()));
+
+ // Paths taken from hunspell-1.2.8/src/tools/hunspell.cxx
+#ifdef Q_OS_WIN32
+ dict_paths << "C:/Hunspell";
+#else
+ dict_paths <<
+ "/usr/local/share/hunspell" <<
+ "/usr/share/myspell" <<
+ "/usr/share/myspell/dicts";
+#endif
+
+ return dict_paths;
+}
+
+void HunSpellChecker::setActiveLanguages(const QList<QString>& langs)
+{
+ // Free all spellers not needed anymore.
+ for (HunSpellers::iterator it = spellers_.begin(); it != spellers_.end(); )
+ {
+ if (!langs.contains(it.key()))
+ {
+ delete it.value().speller;
+ it = spellers_.erase(it);
+ }
+ else
+ ++it;
+ }
+
+ QStringList dict_paths(getDictSearchPaths());
+
+ for (QStringList::const_iterator dict_path_it = dict_paths.begin(); dict_path_it != dict_paths.end(); ++dict_path_it)
+ {
+ for (QStringList::const_iterator lang_it = langs.begin(); lang_it != langs.end(); ++lang_it)
+ {
+ // Load dictionaries only for those languages that are not present already.
+ if (spellers_.contains(*lang_it))
+ continue;
+
+ // Get all affixes with names beginning with the specified language.
+ QStringList affixes = QDir(*dict_path_it).entryList(QStringList(QString("%1*.aff").arg(*lang_it)), QDir::Files);
+
+ for (QStringList::const_iterator affix_it = affixes.begin(); affix_it != affixes.end(); ++affix_it)
+ {
+ QString base_name(QFileInfo(*affix_it).completeBaseName());
+
+ // Get all dictionaries with names beginning with the affix name.
+ QStringList dicts_all = QDir(*dict_path_it).entryList(QStringList(QString("%1*.dic").arg(base_name)), QDir::Files),
+ dicts_to_add;
+
+ // Add all dictionaries except those that have corresponding more specific affix name: those should be
+ // handled separately, together with its affix file.
+ //
+ // So, for example, having en.aff, en.dic, en_XX.dic, en_YY.aff, en_YY.dic we should get in the result two
+ // hunspell objects, one with affix en.aff and dictionaries en.dic and en_XX.dic and the other one with
+ // affix en_YY.aff and dictionary en_YY.dic
+ for (QStringList::const_iterator dict_it = dicts_all.begin(); dict_it != dicts_all.end(); ++dict_it)
+ {
+ QString matching_affix(QString("%1.aff").arg(QFileInfo(*dict_it).completeBaseName()));
+
+ if (matching_affix == *affix_it || !affixes.contains(matching_affix))
+ dicts_to_add << *dict_it;
+ }
+
+ if (dicts_to_add.size())
+ {
+ Hunspell* speller = new Hunspell(
+ QString("%1/%2").arg(*dict_path_it, *affix_it).toLocal8Bit().constData(),
+ QString("%1/%2").arg(*dict_path_it, dicts_to_add[0]).toLocal8Bit().constData()
+ );
+
+ for (int i = 1; i < dicts_to_add.size(); ++i)
+ speller -> add_dic(QString("%1/%2").arg(*dict_path_it, dicts_to_add[i]).toLocal8Bit().constData());
+
+ spellers_.insert(*lang_it, HunSpellInfo(speller, QTextCodec::codecForName(speller -> get_dic_encoding())));
+ }
+ }
+ }
+ }
+}
diff --git a/net-im/psi/files/hunspellchecker.h b/net-im/psi/files/hunspellchecker.h
new file mode 100644
index 000000000000..8e1e85a59665
--- /dev/null
+++ b/net-im/psi/files/hunspellchecker.h
@@ -0,0 +1,71 @@
+/*
+ * hunspellchecker.h
+ *
+ * Copyright (C) 2009 Alexander Tsvyashchenko
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef HUNSPELLCHECKER_H
+#define HUNSPELLCHECKER_H
+
+#include <QList>
+#include <QString>
+#include <QMap>
+
+#include "spellchecker.h"
+
+class Hunspell;
+
+class HunSpellChecker : public SpellChecker
+{
+public:
+ HunSpellChecker();
+ ~HunSpellChecker();
+ virtual QList<QString> suggestions(const QString&, const QString& lang, unsigned max_sugs);
+ virtual bool isCorrect(const QString&);
+ virtual bool add(const QString& word, const QString& lang);
+ virtual bool available() const;
+ virtual bool writable() const;
+ virtual QList<QString> getAllLanguages() const;
+ virtual void setActiveLanguages(const QList<QString>&);
+
+private:
+ struct HunSpellInfo
+ {
+ QTextCodec* codec;
+ Hunspell* speller;
+
+ HunSpellInfo(Hunspell* speller, QTextCodec* codec):
+ speller(speller), codec(codec) {}
+ };
+
+ typedef QMap<QString, HunSpellInfo> HunSpellers;
+
+ HunSpellers spellers_;
+ QList<QString> all_langs_;
+
+private:
+ void clearSpellers();
+ QList<QString> getDictSearchPaths() const;
+};
+
+#endif
diff --git a/net-im/psi/files/patch-git_4b838c0 b/net-im/psi/files/patch-git_4b838c0
deleted file mode 100644
index 29c7aab62d0b..000000000000
--- a/net-im/psi/files/patch-git_4b838c0
+++ /dev/null
@@ -1,15 +0,0 @@
-From 4b838c04fa90ab56974bc4c57aaf7d4e80b8fff8 Mon Sep 17 00:00:00 2001
-From: Vitozz <thetvg@gmail.com>
-Date: Mon, 28 May 2018 20:45:04 +0300
-Subject: [PATCH] Try to fix build with qt-5.11
-
---- src/accountmanagedlg.cpp.orig
-+++ src/accountmanagedlg.cpp
-@@ -28,6 +28,7 @@
- #include <QTimer>
- #include <QHeaderView>
- #include <QDropEvent>
-+#include <QButtonGroup>
-
- #include "psicon.h"
- #include "psiaccount.h"
diff --git a/net-im/psi/files/patch-src_libpsi_tools_spellchecker_spellchecker.cpp b/net-im/psi/files/patch-src_libpsi_tools_spellchecker_spellchecker.cpp
new file mode 100644
index 000000000000..d63a2c9540bc
--- /dev/null
+++ b/net-im/psi/files/patch-src_libpsi_tools_spellchecker_spellchecker.cpp
@@ -0,0 +1,37 @@
+--- src/libpsi/tools/spellchecker/spellchecker.cpp.orig 2018-11-02 00:37:04 UTC
++++ src/libpsi/tools/spellchecker/spellchecker.cpp
+@@ -48,7 +48,7 @@ SpellChecker* SpellChecker::instance()
+ #elif defined(HAVE_ASPELL)
+ instance_ = new ASpellChecker();
+ #elif defined(HAVE_HUNSPELL)
+- instance_ = new HunspellChecker();
++ instance_ = new HunSpellChecker();
+ #else
+ instance_ = new SpellChecker();
+ #endif
+@@ -80,14 +80,23 @@ bool SpellChecker::isCorrect(const QString&)
+ return true;
+ }
+
+-QList<QString> SpellChecker::suggestions(const QString&)
++QList<QString> SpellChecker::suggestions(const QString&, const QString&, unsigned)
+ {
+ return QList<QString>();
+ }
+
+-bool SpellChecker::add(const QString&)
++bool SpellChecker::add(const QString&, const QString&)
+ {
+ return false;
++}
++
++QList<QString> SpellChecker::getAllLanguages() const
++{
++ return QList<QString>();
++}
++
++void SpellChecker::setActiveLanguages(const QList<QString>&)
++{
+ }
+
+ SpellChecker* SpellChecker::instance_ = NULL;
diff --git a/net-im/psi/files/patch-src_libpsi_tools_spellchecker_spellchecker.h b/net-im/psi/files/patch-src_libpsi_tools_spellchecker_spellchecker.h
new file mode 100644
index 000000000000..bbab53d364dc
--- /dev/null
+++ b/net-im/psi/files/patch-src_libpsi_tools_spellchecker_spellchecker.h
@@ -0,0 +1,19 @@
+--- src/libpsi/tools/spellchecker/spellchecker.h.orig 2018-11-02 00:37:04 UTC
++++ src/libpsi/tools/spellchecker/spellchecker.h
+@@ -37,12 +37,11 @@ class SpellChecker : public QObject (public)
+ static SpellChecker* instance();
+ virtual bool available() const;
+ virtual bool writable() const;
+- virtual QList<QString> suggestions(const QString&);
++ virtual QList<QString> suggestions(const QString& word, const QString& lang, unsigned max_sugs);
+ virtual bool isCorrect(const QString&);
+- virtual bool add(const QString&);
+-
+- virtual void setActiveLanguages(const QList<QString>& ) {}
+- virtual QList<QString> getAllLanguages() const { return QList<QString>(); }
++ virtual bool add(const QString& word, const QString& lang);
++ virtual QList<QString> getAllLanguages() const;
++ virtual void setActiveLanguages(const QList<QString>&);
+
+ protected:
+ SpellChecker();
diff --git a/net-im/psi/files/patch-src_msgmle.cpp b/net-im/psi/files/patch-src_msgmle.cpp
new file mode 100644
index 000000000000..1166d2cdd830
--- /dev/null
+++ b/net-im/psi/files/patch-src_msgmle.cpp
@@ -0,0 +1,116 @@
+--- src/msgmle.cpp.orig 2018-11-02 00:15:39 UTC
++++ src/msgmle.cpp
+@@ -257,12 +257,36 @@ bool ChatEdit::checkSpellingGloballyEnabled()
+ return (SpellChecker::instance()->available() && PsiOptions::instance()->getOption("options.ui.spell-check.enabled").toBool());
+ }
+
++QStringList ChatEdit::checkSpellingActiveLanguages()
++{
++ return PsiOptions::instance()->getOption("options.ui.spell-check.langs").toString().split(QRegExp("\\s+|,|\\:"), QString::SkipEmptyParts);
++}
++
++unsigned ChatEdit::checkSpellingMaxSuggestions()
++{
++ return PsiOptions::instance()->getOption("options.ui.spell-check.maxsugs").toString().toInt();
++}
++
+ void ChatEdit::setCheckSpelling(bool b)
+ {
+ check_spelling_ = b;
+ if (check_spelling_) {
+ if (!spellhighlighter_)
+ spellhighlighter_ = new SpellHighlighter(document());
++ all_langs_ = SpellChecker::instance()->getAllLanguages();
++ langs_ = checkSpellingActiveLanguages();
++ // No langs specified in options?
++ if (langs_.isEmpty()) {
++ QString env_lang(getenv("LANG"));
++ // Let's try to use the language specified in environment ...
++ if (!env_lang.isEmpty() && all_langs_.contains(env_lang))
++ langs_.append(env_lang);
++ else // ... still no luck? Will use all available languages then.
++ langs_ = all_langs_;
++ }
++ SpellChecker::instance()->setActiveLanguages(langs_);
++ // If zero, means no limit (empty option also translates to zero).
++ max_sugs_ = checkSpellingMaxSuggestions();
+ }
+ else {
+ delete spellhighlighter_;
+@@ -335,19 +359,34 @@ void ChatEdit::contextMenuEvent(QContextMenuEvent *e)
+ tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
+ QString selected_word = tc.selectedText();
+ if (!selected_word.isEmpty() && !QRegExp("\\d+").exactMatch(selected_word) && !SpellChecker::instance()->isCorrect(selected_word)) {
+- QList<QString> suggestions = SpellChecker::instance()->suggestions(selected_word);
+- if (!suggestions.isEmpty() || SpellChecker::instance()->writable()) {
+- QMenu spell_menu;
++ QMenu spell_menu;
++ foreach (QString lang, langs_) {
++ QList<QString> suggestions = SpellChecker::instance()->suggestions(selected_word, lang, max_sugs_);
+ if (!suggestions.isEmpty()) {
++ QAction* lang_name = spell_menu.addAction(tr("Language") + ": " + lang);
++ lang_name->setDisabled(true);
+ foreach(QString suggestion, suggestions) {
+ QAction* act_suggestion = spell_menu.addAction(suggestion);
+ connect(act_suggestion,SIGNAL(triggered()),SLOT(applySuggestion()));
+ }
+ spell_menu.addSeparator();
+ }
++ }
++ if (!spell_menu.isEmpty() || SpellChecker::instance()->writable() || !all_langs_.isEmpty()) {
+ if (SpellChecker::instance()->writable()) {
+- QAction* act_add = spell_menu.addAction(tr("Add to dictionary"));
+- connect(act_add,SIGNAL(triggered()),SLOT(addToDictionary()));
++ foreach (QString lang, langs_) {
++ QAction* act_add = spell_menu.addAction(tr("Add to dictionary") + ": " + lang);
++ act_add->setData(lang);
++ connect(act_add,SIGNAL(triggered()),SLOT(addToDictionary()));
++ }
++ spell_menu.addSeparator();
++ foreach (QString lang, all_langs_) {
++ QAction* act_lang_sel = spell_menu.addAction(tr("Use language") + ": " + lang);
++ act_lang_sel->setCheckable(true);
++ act_lang_sel->setChecked(langs_.contains(lang));
++ act_lang_sel->setData(lang);
++ connect(act_lang_sel,SIGNAL(triggered()),SLOT(changedUseLang()));
++ }
+ }
+ spell_menu.exec(QCursor::pos());
+ e->accept();
+@@ -397,18 +436,35 @@ void ChatEdit::applySuggestion()
+ */
+ void ChatEdit::addToDictionary()
+ {
++ QAction* action = static_cast<QAction*>(sender());
+ QTextCursor tc = cursorForPosition(last_click_);
+ int current_position = textCursor().position();
+
+ // Get the selected word
+ tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
+ tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
+- SpellChecker::instance()->add(tc.selectedText());
++ SpellChecker::instance()->add(tc.selectedText(), action->data().toString());
+
+ // Put the cursor where it belongs
+ tc.clearSelection();
+ tc.setPosition(current_position);
+ setTextCursor(tc);
++
++ spellhighlighter_->rehighlight();
++}
++
++void ChatEdit::changedUseLang()
++{
++ QAction* action = static_cast<QAction*>(sender());
++ QString lang = action->data().toString();
++
++ if (langs_.contains(lang))
++ langs_.removeAt(langs_.indexOf(lang));
++ else
++ langs_.append(lang);
++
++ SpellChecker::instance()->setActiveLanguages(langs_);
++ spellhighlighter_->rehighlight();
+ }
+
+ void ChatEdit::optionsChanged()
diff --git a/net-im/psi/files/patch-src_msgmle.h b/net-im/psi/files/patch-src_msgmle.h
new file mode 100644
index 000000000000..90650541852b
--- /dev/null
+++ b/net-im/psi/files/patch-src_msgmle.h
@@ -0,0 +1,28 @@
+--- src/msgmle.h.orig 2018-11-02 00:15:39 UTC
++++ src/msgmle.h
+@@ -54,6 +54,8 @@ class ChatEdit : public QTextEdit (public)
+ void setFont(const QFont &);
+
+ static bool checkSpellingGloballyEnabled();
++ static QStringList checkSpellingActiveLanguages();
++ static unsigned checkSpellingMaxSuggestions();
+ void setCheckSpelling(bool);
+ XMPP::HTMLElement toHTMLElement();
+ bool isCorrection() { return correction; }
+@@ -71,6 +73,7 @@ public slots:
+ protected slots:
+ void applySuggestion();
+ void addToDictionary();
++ void changedUseLang();
+ void optionsChanged();
+ void showHistoryMessageNext();
+ void showHistoryMessagePrev();
+@@ -91,6 +94,8 @@ protected slots: (protected)
+ private:
+ QWidget *dialog_;
+ bool check_spelling_;
++ QList<QString> langs_, all_langs_;
++ unsigned max_sugs_;
+ SpellHighlighter* spellhighlighter_;
+ QPoint last_click_;
+ int previous_position_;
diff --git a/net-im/psi/files/patch-src_options_opt__advanced.cpp b/net-im/psi/files/patch-src_options_opt__advanced.cpp
new file mode 100644
index 000000000000..f338524023c9
--- /dev/null
+++ b/net-im/psi/files/patch-src_options_opt__advanced.cpp
@@ -0,0 +1,66 @@
+--- src/options/opt_advanced.cpp.orig 2018-11-02 00:15:39 UTC
++++ src/options/opt_advanced.cpp
+@@ -45,6 +45,8 @@ QWidget *OptionsTabAdvanced::widget()
+ #endif
+
+ d->ck_spell->setEnabled(SpellChecker::instance()->available());
++ d->le_spellLangs->setEnabled(SpellChecker::instance()->available());
++ d->le_spellMaxSugs->setEnabled(SpellChecker::instance()->available());
+
+ d->ck_messageevents->setWhatsThis(
+ tr("Enables the sending and requesting of message events such as "
+@@ -60,6 +62,12 @@ QWidget *OptionsTabAdvanced::widget()
+ tr("Enables remote controlling your client from other locations"));
+ d->ck_spell->setWhatsThis(
+ tr("Check this option if you want your spelling to be checked"));
++ d->le_spellLangs->setWhatsThis(
++ tr("List here all languages you want your spell checker to use"
++ " when checking your spelling."));
++ d->le_spellMaxSugs->setWhatsThis(
++ tr("Maximal number of suggestion words per language you want to see"
++ " in context menu when the word is misspelled."));
+ d->ck_contactsMessageFormatting->setWhatsThis(
+ tr("If enabled, Psi will display incoming messages formatted in the style specified by the contact"));
+ d->ck_autocopy->setWhatsThis(
+@@ -99,6 +107,10 @@ QWidget *OptionsTabAdvanced::widget()
+ connect(d->ck_messageevents,SIGNAL(toggled(bool)),d->ck_sendComposingEvents,SLOT(setEnabled(bool)));
+ d->ck_inactiveevents->setEnabled(d->ck_messageevents->isChecked());
+ d->ck_sendComposingEvents->setEnabled(d->ck_messageevents->isChecked());
++ connect(d->ck_spell,SIGNAL(toggled(bool)),d->le_spellLangs,SLOT(setEnabled(bool)));
++ connect(d->ck_spell,SIGNAL(toggled(bool)),d->le_spellMaxSugs,SLOT(setEnabled(bool)));
++ d->le_spellLangs->setEnabled(d->ck_spell->isChecked());
++ d->le_spellMaxSugs->setEnabled(d->ck_spell->isChecked());
+
+ return w;
+ }
+@@ -116,8 +128,11 @@ void OptionsTabAdvanced::applyOptions()
+ PsiOptions::instance()->setOption("options.ui.notifications.send-receipts", d->ck_sendReceipts->isChecked());
+ PsiOptions::instance()->setOption("options.messages.dont-send-composing-events", d->ck_sendComposingEvents->isChecked());
+ PsiOptions::instance()->setOption("options.external-control.adhoc-remote-control.enable", d->ck_rc->isChecked());
+- if ( SpellChecker::instance()->available() )
++ if ( SpellChecker::instance()->available() ) {
+ PsiOptions::instance()->setOption("options.ui.spell-check.enabled",d->ck_spell->isChecked());
++ PsiOptions::instance()->setOption("options.ui.spell-check.langs", d->le_spellLangs->text());
++ PsiOptions::instance()->setOption("options.ui.spell-check.maxsugs", d->le_spellMaxSugs->text());
++ }
+ PsiOptions::instance()->setOption("options.html.chat.render", d->ck_contactsMessageFormatting->isChecked());
+ PsiOptions::instance()->setOption("options.ui.automatically-copy-selected-text", d->ck_autocopy->isChecked());
+ PsiOptions::instance()->setOption("options.ui.contactlist.use-single-click", d->ck_singleclick->isChecked());
+@@ -145,10 +160,15 @@ void OptionsTabAdvanced::restoreOptions()
+ d->ck_sendReceipts->setChecked( PsiOptions::instance()->getOption("options.ui.notifications.send-receipts").toBool() );
+ d->ck_sendComposingEvents->setChecked( PsiOptions::instance()->getOption("options.messages.dont-send-composing-events").toBool() );
+ d->ck_rc->setChecked( PsiOptions::instance()->getOption("options.external-control.adhoc-remote-control.enable").toBool() );
+- if ( !SpellChecker::instance()->available() )
++ if ( !SpellChecker::instance()->available() ) {
+ d->ck_spell->setChecked(false);
+- else
++ d->le_spellLangs->setText("");
++ d->le_spellMaxSugs->setText("");
++ } else {
+ d->ck_spell->setChecked(PsiOptions::instance()->getOption("options.ui.spell-check.enabled").toBool());
++ d->le_spellLangs->setText(PsiOptions::instance()->getOption("options.ui.spell-check.langs").toString());
++ d->le_spellMaxSugs->setText(PsiOptions::instance()->getOption("options.ui.spell-check.maxsugs").toString());
++ }
+ d->ck_contactsMessageFormatting->setChecked(PsiOptions::instance()->getOption("options.html.chat.render").toBool());
+ d->ck_autocopy->setChecked( PsiOptions::instance()->getOption("options.ui.automatically-copy-selected-text").toBool() );
+ d->ck_singleclick->setChecked( PsiOptions::instance()->getOption("options.ui.contactlist.use-single-click").toBool() );
diff --git a/net-im/psi/files/patch-src_options_opt__advanced.ui b/net-im/psi/files/patch-src_options_opt__advanced.ui
new file mode 100644
index 000000000000..e1076f2b0cf4
--- /dev/null
+++ b/net-im/psi/files/patch-src_options_opt__advanced.ui
@@ -0,0 +1,29 @@
+--- src/options/opt_advanced.ui.orig 2018-11-02 00:15:39 UTC
++++ src/options/opt_advanced.ui
+@@ -72,6 +72,26 @@
+ </widget>
+ </item>
+ <item>
++ <widget class="QLabel" name="TextLabel3" >
++ <property name="text" >
++ <string>List of active spellchecker languages:</string>
++ </property>
++ </widget>
++ </item>
++ <item>
++ <widget class="QLineEdit" name="le_spellLangs" />
++ </item>
++ <item>
++ <widget class="QLabel" name="TextLabel4" >
++ <property name="text" >
++ <string>Maximum suggestions per language:</string>
++ </property>
++ </widget>
++ </item>
++ <item>
++ <widget class="QLineEdit" name="le_spellMaxSugs" />
++ </item>
++ <item>
+ <widget class="QCheckBox" name="ck_contactsMessageFormatting" >
+ <property name="text" >
+ <string>Use contacts' message formatting</string>