From 4ef710cfcc4aa4f171426ef55cbece00cd69af62 Mon Sep 17 00:00:00 2001
From: Vlad Voicu <vladvoic@gmail.com>
Date: Mon, 28 Nov 2011 18:37:32 +0200
Subject: Initial, buggy version of a minimal spell-checker


diff --git a/SwifTools/SConscript b/SwifTools/SConscript
index 3b25269..6f49e7f 100644
--- a/SwifTools/SConscript
+++ b/SwifTools/SConscript
@@ -7,7 +7,7 @@ Import("env")
 if env["SCONS_STAGE"] == "flags" :
 	env["SWIFTOOLS_FLAGS"] = {
 			"LIBPATH": [Dir(".")],
-			"LIBS": ["SwifTools"]
+			"LIBS": ["SwifTools", "libaspell"]
 		}
 
 ################################################################################
@@ -29,6 +29,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Linkify.cpp",
 			"TabComplete.cpp",
 			"LastLineTracker.cpp",
+			"SpellChecker.cpp",
 		]
 
 	if swiftools_env.get("HAVE_SPARKLE", 0) :
diff --git a/SwifTools/SpellChecker.cpp b/SwifTools/SpellChecker.cpp
new file mode 100644
index 0000000..8c9aeda
--- /dev/null
+++ b/SwifTools/SpellChecker.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2011 Voicu Vlad
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <SwifTools/SpellChecker.h>
+
+#include <algorithm>
+#include <aspell.h>
+#include <boost/algorithm/string.hpp>
+
+
+namespace Swift {
+
+SpellChecker::SpellChecker() {
+	AspellCanHaveError* ret;
+	config_ = new_aspell_config();
+	ret = new_aspell_speller(config_);
+	if (aspell_error(ret) != 0) {
+		//TODO(vladv): Proper log the error
+		//printf("Error: %s\n",aspell_error_message(ret));
+		delete_aspell_can_have_error(ret);
+	}
+	speller_ = to_aspell_speller(ret);
+}
+
+bool SpellChecker::isCorrect(const std::string& word) {
+	if (!word.empty()) {
+		int aspell_error = aspell_speller_check(speller_, word.c_str(), -1);
+		if (aspell_error == 1) {
+			return true;
+		} else {
+			//TODO(vladv): Proper log the error
+		}
+	}
+	return false;
+}
+
+void SpellChecker::getSuggestions(const std::string& word, std::vector<std::string>& list) {
+	const AspellWordList *alist;
+	if (!word.empty()) {
+		alist = aspell_speller_suggest(speller_, word.c_str(), -1);
+		AspellStringEnumeration *els = aspell_word_list_elements(alist);
+		const char* aword;
+		while (((aword = aspell_string_enumeration_next(els)) != 0) && list.size() <= 5) {
+			list.push_back(std::string(aword));
+		}
+	}
+}
+
+}
diff --git a/SwifTools/SpellChecker.h b/SwifTools/SpellChecker.h
new file mode 100644
index 0000000..3a9ff38
--- /dev/null
+++ b/SwifTools/SpellChecker.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2011 Voicu Vlad
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <vector>
+#include <boost/algorithm/string.hpp>
+
+#pragma once
+
+class AspellSpeller;
+class AspellConfig;
+
+namespace Swift {
+	class SpellChecker {
+		public:
+			SpellChecker();
+			bool isCorrect(const std::string& word);
+			void getSuggestions(const std::string& word, std::vector<std::string>& list);
+		private:
+			AspellSpeller* speller_;
+			AspellConfig* config_;
+	};
+}
diff --git a/Swift/QtUI/QtTextEdit.cpp b/Swift/QtUI/QtTextEdit.cpp
index 3a62325..e1fdd26 100644
--- a/Swift/QtUI/QtTextEdit.cpp
+++ b/Swift/QtUI/QtTextEdit.cpp
@@ -4,18 +4,28 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
+#include <SwifTools/SpellChecker.h>
+
 #include <Swift/QtUI/QtTextEdit.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
 
 #include <QFontMetrics>
 #include <QKeyEvent>
+#include <QDebug>
+#include <QMenu>
 
 namespace Swift {
 
 QtTextEdit::QtTextEdit(QWidget* parent) : QTextEdit(parent){
 	connect(this, SIGNAL(textChanged()), this, SLOT(handleTextChanged()));
 	handleTextChanged();
+	checker_ = new SpellChecker();
 };
 
+QtTextEdit::~QtTextEdit() {
+	delete checker_;
+}
+
 void QtTextEdit::keyPressEvent(QKeyEvent* event) {
 	int key = event->key();
 	Qt::KeyboardModifiers modifiers = event->modifiers();
@@ -35,13 +45,37 @@ void QtTextEdit::keyPressEvent(QKeyEvent* event) {
 		emit unhandledKeyPressEvent(event);
 	}
 	else if ((key == Qt::Key_Up)
-			   || (key == Qt::Key_Down)
-	){
+			   || (key == Qt::Key_Down)) {
 		emit unhandledKeyPressEvent(event);
 		QTextEdit::keyPressEvent(event);
 	}
 	else {
 		QTextEdit::keyPressEvent(event);
+		underlineMisspells();
+	}
+}
+
+void QtTextEdit::underlineMisspells() {
+	QTextCharFormat spellingErrorFormat;
+	QTextCharFormat normalFormat;
+	spellingErrorFormat.setUnderlineColor(QColor(Qt::red));
+	spellingErrorFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline);
+	QTextCursor cursor = textCursor();
+	QString word;
+	cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 1);
+	cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1);
+	word = cursor.selectedText();
+	// Qt handles punctuation as words. Workaround that.
+	while (!word.isEmpty() && !word.at(0).isLetter()) {
+		cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 2);
+		cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1);
+		word = cursor.selectedText();
+	}
+	if ((!word.isEmpty()) && !checker_->isCorrect(word.toStdString())) {
+		cursor.mergeCharFormat(spellingErrorFormat);
+		cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::MoveAnchor, 1);
+		cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor, 1);
+		cursor.setCharFormat(normalFormat);
 	}
 }
 
@@ -53,6 +87,14 @@ void QtTextEdit::handleTextChanged() {
 	}
 }
 
+void QtTextEdit::handleReplaceMisspellWord(const QString& word, const QPoint& position) {
+	QTextCursor cursor = cursorForPosition(position);
+	cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 1);
+	cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1);
+	QTextCharFormat normalFormat;
+	cursor.insertText(word, normalFormat);
+}
+
 QSize QtTextEdit::sizeHint() const {
 	QFontMetrics inputMetrics(currentFont());
 	QRect horizontalBounds = contentsRect().adjusted(0,0,0,9999);
@@ -66,7 +108,26 @@ QSize QtTextEdit::sizeHint() const {
 	//return QSize(QTextEdit::sizeHint().width(), lineHeight * numberOfLines);
 }
 
-}
+void QtTextEdit::contextMenuEvent(QContextMenuEvent* event) {
+	QTextCursor cursor = cursorForPosition(event->pos());
+	cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 1);
+	cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1);
+	std::vector<std::string> wordList;
+	checker_->getSuggestions(Q2PSTRING(cursor.selectedText()), wordList);
 
+	std::vector<QAction*> replaceWordActions;
+	QMenu *menu = createStandardContextMenu();
+	for (std::vector<std::string>::iterator it = wordList.begin(); it != wordList.end(); ++it) {
+		replaceWordActions.push_back(menu->addAction(tr(it->c_str())));
+	}
 
+	QAction* result = menu->exec(event->globalPos());
+	for (std::vector<QAction*>::iterator it = replaceWordActions.begin(); it != replaceWordActions.end(); ++it) {
+		if (*it == result) {
+			handleReplaceMisspellWord((*it)->text(), event->pos());
+		}
+	}
+	delete menu;
+}
 
+}
diff --git a/Swift/QtUI/QtTextEdit.h b/Swift/QtUI/QtTextEdit.h
index 075728b..a940879 100644
--- a/Swift/QtUI/QtTextEdit.h
+++ b/Swift/QtUI/QtTextEdit.h
@@ -5,20 +5,30 @@
  */
 
 #pragma once
+
 #include <QTextEdit>
 
 namespace Swift {
+	class SpellChecker;
 	class QtTextEdit : public QTextEdit {
 		Q_OBJECT
 	public:
 		QtTextEdit(QWidget* parent = 0);
+		virtual ~QtTextEdit();
 		virtual QSize sizeHint() const;
 	signals:
+		void wordCorrected(QString& word);
 		void returnPressed();
 		void unhandledKeyPressEvent(QKeyEvent* event);
+	public slots:
+		void handleReplaceMisspellWord(const QString& word, const QPoint& position);
 	protected:
 		virtual void keyPressEvent(QKeyEvent* event);
+		virtual void contextMenuEvent(QContextMenuEvent* event);
 	private slots:
 		void handleTextChanged();
+	private:
+		SpellChecker *checker_;
+		void underlineMisspells();
 	};
 }
-- 
cgit v0.10.2-6-g49f6