Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 7 Apr 2019 14:45:50 +0000 (UTC)
From:      Alexey Dokuchaev <danfe@FreeBSD.org>
To:        ports-committers@freebsd.org, svn-ports-all@freebsd.org, svn-ports-head@freebsd.org
Subject:   svn commit: r498280 - in head/net-im/psi: . files
Message-ID:  <201904071445.x37EjojV079634@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: danfe
Date: Sun Apr  7 14:45:50 2019
New Revision: 498280
URL: https://svnweb.freebsd.org/changeset/ports/498280

Log:
  - 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

Added:
  head/net-im/psi/files/hunspellchecker.cpp   (contents, props changed)
  head/net-im/psi/files/hunspellchecker.h   (contents, props changed)
  head/net-im/psi/files/patch-src_libpsi_tools_spellchecker_spellchecker.cpp   (contents, props changed)
  head/net-im/psi/files/patch-src_libpsi_tools_spellchecker_spellchecker.h   (contents, props changed)
  head/net-im/psi/files/patch-src_msgmle.cpp   (contents, props changed)
  head/net-im/psi/files/patch-src_msgmle.h   (contents, props changed)
  head/net-im/psi/files/patch-src_options_opt__advanced.cpp   (contents, props changed)
  head/net-im/psi/files/patch-src_options_opt__advanced.ui   (contents, props changed)
Deleted:
  head/net-im/psi/files/patch-git_4b838c0
Modified:
  head/net-im/psi/Makefile
  head/net-im/psi/distinfo

Modified: head/net-im/psi/Makefile
==============================================================================
--- head/net-im/psi/Makefile	Sun Apr  7 14:23:24 2019	(r498279)
+++ head/net-im/psi/Makefile	Sun Apr  7 14:45:50 2019	(r498280)
@@ -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,' \

Modified: head/net-im/psi/distinfo
==============================================================================
--- head/net-im/psi/distinfo	Sun Apr  7 14:23:24 2019	(r498279)
+++ head/net-im/psi/distinfo	Sun Apr  7 14:45:50 2019	(r498280)
@@ -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

Added: head/net-im/psi/files/hunspellchecker.cpp
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/net-im/psi/files/hunspellchecker.cpp	Sun Apr  7 14:45:50 2019	(r498280)
@@ -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())));
+				}
+			}
+		}
+	}
+}

Added: head/net-im/psi/files/hunspellchecker.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/net-im/psi/files/hunspellchecker.h	Sun Apr  7 14:45:50 2019	(r498280)
@@ -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

Added: head/net-im/psi/files/patch-src_libpsi_tools_spellchecker_spellchecker.cpp
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/net-im/psi/files/patch-src_libpsi_tools_spellchecker_spellchecker.cpp	Sun Apr  7 14:45:50 2019	(r498280)
@@ -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;

Added: head/net-im/psi/files/patch-src_libpsi_tools_spellchecker_spellchecker.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/net-im/psi/files/patch-src_libpsi_tools_spellchecker_spellchecker.h	Sun Apr  7 14:45:50 2019	(r498280)
@@ -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();

Added: head/net-im/psi/files/patch-src_msgmle.cpp
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/net-im/psi/files/patch-src_msgmle.cpp	Sun Apr  7 14:45:50 2019	(r498280)
@@ -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()

Added: head/net-im/psi/files/patch-src_msgmle.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/net-im/psi/files/patch-src_msgmle.h	Sun Apr  7 14:45:50 2019	(r498280)
@@ -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_;

Added: head/net-im/psi/files/patch-src_options_opt__advanced.cpp
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/net-im/psi/files/patch-src_options_opt__advanced.cpp	Sun Apr  7 14:45:50 2019	(r498280)
@@ -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() );

Added: head/net-im/psi/files/patch-src_options_opt__advanced.ui
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/net-im/psi/files/patch-src_options_opt__advanced.ui	Sun Apr  7 14:45:50 2019	(r498280)
@@ -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>



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201904071445.x37EjojV079634>