Click here to Skip to main content
15,885,546 members
Articles / DevOps / Load Testing

NHunspell Component for Visual Studio

Rate me:
Please Sign up or sign in to vote.
4.90/5 (31 votes)
1 Sep 2013CPOL6 min read 113.4K   4.1K   46  
NHunspell component example for WindowsForms
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using NHunspell;
using NHunspellComponent.Spelling.Events;
using NHunspellComponent.Spelling.Interfaces;
using NHunspellComponent.SupportClasses;

namespace NHunspellComponent.Spelling
{
   public class NHunspellWrapper
   {
      public delegate void EmptyNotificartion();

      public event EmptyNotificartion PropertiesChanged;
      public event Options.OptionsChangedHandle OptionsChanged;

      public delegate void AddedWordHandler(object sender, SpellingEventArgs e);

      public delegate void IgnoredWordHandler(object sender, IgnoreEventArgs e);

      public delegate void ChangedWordHandler(object sender, ChangeEventArgs e);

      private readonly Hunspell hunspell;
      private readonly ISpellingWindow spellingForm;
      private ISpellingControl editor;

      private int maxSuggestions;
      private int maxSuggestionsBackup;
      private List<string> ignoreList;
      private Options options;
      //bool spellingWindowIsOpen;
      //It shouldn't do any underlinings check when we are working in SuggestionsWindow.

      private readonly Regex wordEx = new Regex(@"\b[A-Za-z']+\b", RegexOptions.Compiled);
      //private Regex wordEx = new Regex(@"\b[A-Za-z0-9_'А-я]+\b", RegexOptions.Compiled);
      //private Regex numberRegex = new Regex(@"\b^[0-9][0-9]*\b", RegexOptions.Compiled);
      private readonly Regex numberRegex = new Regex(@"\b^\d\d*\b", RegexOptions.Compiled);
      private readonly Regex digitRegex = new Regex(@".\d.", RegexOptions.Compiled);

      #region ctors

      //
      // Constructor used by default. Always keep one in your classes until you understand it's not necessary.
      //
      public NHunspellWrapper()
         : this("en_US.aff", "en_US.dic", null, new SpellingFormBasic())
      {
      }

      /// <param name="affFile">"en_us.aff"</param>
      /// <param name="dicFile">"en_us.dic"</param>
      public NHunspellWrapper(string affFile, string dicFile,
                              ISpellingControl textEditor, ISpellingWindow spellForm)
      {
         try
         {
            hunspell = new Hunspell(affFile, dicFile);
            spellingForm = spellForm;
            InitializeSpellingForm();
            Editor = textEditor;
         }
         catch (Exception e)
         {
            MessageBox.Show(e.Message + "\nAutoSpellchecking won't work. Place dictionary file to program directory."
                            , "Error loading spellchecker.",
                            MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1,
                            MessageBoxOptions.DefaultDesktopOnly);
         }
      }

      #endregion

      [ThreadStatic]
      private static NHunspellWrapper _instance;

      public static NHunspellWrapper Instance
      {
         //[STAThread]
         get
         {
            if (_instance == null)
               _instance = new NHunspellWrapper();
            return _instance;
         }
      }

      public ISpellingControl Editor
      {
         get
         {
            return editor;
         }
         set
         {
            if (editor != null)
            {
               editor.ContextMenuStrip.Opening -= ContextMenuStrip_Opening;
               editor.ContextMenuStrip.Closing -= ContextMenuStrip_Closing;
            }

            if (value != null)
            {
               editor = value;

               if (editor.ContextMenuStrip == null)
               {
                  editor.ContextMenuStrip = new ContextMenuStrip();
                  editor.ContextMenuStrip.ShowImageMargin = false;
               }
               editor.ContextMenuStrip.Opening += ContextMenuStrip_Opening;
               editor.ContextMenuStrip.Closing += ContextMenuStrip_Closing;

               editor.Disposed += editor_Disposed;
            }
         }
      }

      public Options OptionsOfCurEditor
      {
         set
         {
            options = value;
         }
      }

      private void InitializeSpellingForm()
      {
         spellingForm.VisibleChanged += spellingForm_VisibleChanged;
         spellingForm.Closing += spellingForm_Closing;
         spellingForm.AdddedWord += spellingForm_AdddedWord;
         spellingForm.IgnoredWord += spellingForm_IgnoredWord;
         spellingForm.ChangedWord += spellingForm_ChangedWord;
         spellingForm.OptionsChanged += spellingForm_OptionsChanged;
      }

      private void spellingForm_AdddedWord(object sender, SpellingEventArgs e)
      {
         if (hunspell != null) hunspell.Add(e.Word);
         UpdateSpellingForm(e.TextIndex);
         CallPropertiesChanged();
      }

      private void spellingForm_IgnoredWord(object sender, IgnoreEventArgs e)
      {
         AddToIgnoreList(e.Word);
         UpdateSpellingForm(e.TextIndex);
         CallPropertiesChanged();
      }

      private void AddToIgnoreList(string word)
      {
         if (!ignoreList.Contains(word))
         {
            ignoreList.Add(word);
         }
      }

      private void spellingForm_ChangedWord(object sender, ChangeEventArgs e)
      {
         if (!e.ApplyToAllWords)
         {
            ReplaceWord(e.TextIndex, e.NewWord);
            UpdateSpellingForm(e.TextIndex);
         }
         else
         {
            List<int> wordIndeces = new List<int>();
            MatchCollection mcol = CalculateWords(editor.Text);
            foreach (Match m in mcol)
            {
               if (m.Value == e.Word)
               {
                  wordIndeces.Add(GetWordIndex(editor.Text, m.Index));
               }
            }
            foreach (int curWordTextIndex in wordIndeces)
            {
               ReplaceWord(GetTextIndexFromWordIndex(editor.Text, curWordTextIndex), e.NewWord);
            }
            UpdateSpellingForm(e.TextIndex);
         }
         CallPropertiesChanged();
      }

      private void spellingForm_OptionsChanged(Options opts)
      {
         options = opts;
         if (OptionsChanged != null)
         {
            OptionsChanged(options);
         }
      }

      private void CallPropertiesChanged()
      {
         if (PropertiesChanged != null)
         {
            PropertiesChanged();
         }
         editor.Invalidate(true);
      }

      private void UpdateSpellingForm(int index)
      {
         spellingForm.TextBeingSpelled = editor.Text;
         SpellingFormHighlight(index);
      }

      private void SpellingFormHighlight(int startIndex)
      {
         Word firstWord = GetFirstMisspelledWord(editor.Text, startIndex);
         spellingForm.HighlightedRange = new CharacterRange(firstWord.Start, firstWord.Length);
         if (hunspell != null) spellingForm.SuggestionsInBox = hunspell.Suggest(firstWord.Text ?? "");
      }

      private void spellingForm_Closing(object sender, System.ComponentModel.CancelEventArgs e)
      {
         if (editor != null)
         {
            editor.Focus();
         }
         CallPropertiesChanged();
         e.Cancel = true;
         spellingForm.Visible = false;
      }

      private void spellingForm_VisibleChanged(object sender, EventArgs e)
      {
         spellingForm.TextBeingSpelled = editor.Text;
         spellingForm.OptionsOfCurEditor = options;

         if (!spellingForm.Visible) //Hidding Form
            spellingForm.Owner = null;
         else //Showing Form
         {
            if (editor != null && editor.Parent != null)
            {
               //Mega feature - thanks to Resharper! Made 1 short line from 3 extra large ones.
               Form editorParent = editor.Parent as Form ?? editor.Parent.Parent as Form;
               spellingForm.Owner = editorParent;
               if (editorParent != null)
                  editorParent.FormClosing += editorParent_FormClosing;
               SpellingFormHighlight(0);
            }
         }
      }

      private List<string> IgnoreList
      {
         get
         {
            if (ignoreList == null)
            {
               ignoreList = new List<string>();
            }
            return ignoreList;
         }
         set { ignoreList = value; }
      }

      private void editor_Disposed(object sender, EventArgs e)
      {
         Editor = null;
         if (spellingForm != null && spellingForm.Visible)
         {
            spellingForm.Visible = false;
         }
      }

      private void editorParent_FormClosing(object sender, FormClosingEventArgs e)
      {
         OnAppClose();
      }

      public void OnAppClose()
      {
         spellingForm.Owner = null;
      }

      private void ContextMenuStrip_Closing(object sender, ToolStripDropDownClosingEventArgs e)
      {
         foreach (ToolStripItem ts in tsList)
            editor.ContextMenuStrip.Items.Remove(ts);
         tsList.Clear();
         //if (e.CloseReason != ToolStripDropDownCloseReason.ItemClicked)
         //   editor.SelectionStart = editor.SelectionStart;
      }

      private readonly List<ToolStripItem> tsList = new List<ToolStripItem>();

      private void ContextMenuStrip_Opening(object sender, System.ComponentModel.CancelEventArgs e)
      {
         //Selected more than 1 word or
         //ToolStripItems List was not cleared. No ContextMenuStrip_Closing fired.
         if (wordEx.Matches(editor.SelectedText).Count > 1 || tsList.Count > 0)
         {
            return;
         }

         Word word = GetWordFromPosition();
         if (!HunspellSpell(word))
         {
            e.Cancel = false;
            BackupMaxSuggestions();
            maxSuggestions = 4;
            if (hunspell != null)
            {
               List<string> Suggestions = hunspell.Suggest(word.Text);
               foreach (string sug in Suggestions)
               {
                  ToolStripItem item = new ToolStripMenuItem(sug, null, new EventHandler(Suggestion_Click));
                  //item.Click += new EventHandler(Suggestion_Click);
                  tsList.Add(item);
                  if (tsList.Count >= maxSuggestions) break;
               }
               if (Suggestions.Count == 0)
               {
                  ToolStripItem item = new ToolStripMenuItem();
                  item.Text = Constants.NoSuggestions;
                  item.Enabled = false;
                  tsList.Add(item);
               }
               else
               {
                  ToolStripItem item = new ToolStripMenuItem("All suggestions", null,
                                                             new EventHandler(CallShowCurrentWordWindow));
                  tsList.Add(item);
               }
            }

            if (tsList.Count > 0)
            {
               if (editor.ContextMenuStrip.Items.Count > 0)
               {
                  tsList.Add(new ToolStripSeparator());
               }
               for (int i = 0; i < tsList.Count; i++)
               {
                  editor.ContextMenuStrip.Items.Insert(i, tsList[i]);
               }
               //editor.ContextMenuStrip.Items.AddRange(tsList.ToArray());
            }
            else
               e.Cancel = true;
         }
      }

      /// <summary>
      /// Check if the word is spelled correctly.
      /// </summary>
      /// <param name="word"></param>
      /// <returns>true if word is correct</returns>
      private bool HunspellSpell(Word word)
      {
         bool result = true;
         if (word.Length > 0)
         {
            bool wordIsNumber = numberRegex.IsMatch(word.Text);
            bool wordContainsDigits = digitRegex.IsMatch(word.Text);
            bool wordIgnored = IgnoreList.Contains(word.Text);
            if (!wordIgnored && !wordIsNumber &&
                (!wordContainsDigits || (wordContainsDigits && !options.IgnoreWordsWithDigits)))
            {
               if (hunspell != null) result = hunspell.Spell(word.Text);
            }
         }
         return result;
      }

      private bool spellCheckAllowed;

      public bool SpellCheckAllowed
      {
         get { return spellCheckAllowed; }
         set
         {
            spellCheckAllowed = value;
            if (spellCheckAllowed == false)
            {
               autoSpellCheck = false;
            }
         }
      }

      private bool autoSpellCheck;

      public bool AutoSpellCheck
      {
         get { return autoSpellCheck; }
         set { autoSpellCheck = value; }
      }

      private void BackupMaxSuggestions()
      {
         maxSuggestionsBackup = this.maxSuggestions;
      }

      private void RestoreMaxSuggestions()
      {
         maxSuggestions = maxSuggestionsBackup;
      }

      private Word GetWordFromPosition()
      {
         return Word.GetWordFromPosition(editor.Text, editor.SelectionStart);
      }

      public CharacterRange GetCharRangeFromPosition(int position)
      {
         Word word = Word.GetWordFromPosition(editor.Text, position);
         return new CharacterRange(word.Start, word.Length);
      }

      private void Suggestion_Click(object sender, EventArgs e)
      {
         ToolStripItem suggestionItem = sender as ToolStripItem;
         if (suggestionItem != null)
         {
            string replacementWord = suggestionItem.Text;
            ReplaceWord(editor.SelectionStart, replacementWord);
         }
      }

      private void ReplaceWord(int position, string replacementWord)
      {
         Word word = Word.GetWordFromPosition(editor.Text, position);
         string tmpText = editor.Text.Remove(word.Start, word.Length);
         editor.Text = tmpText.Insert(word.Start, replacementWord);
         //editor.Select(word.Start, replacementWord.Length);
         //editor.SelectionLength = 0;
         editor.SelectionStart = word.Start + replacementWord.Length;
      }

      public void ShowCheckAllWindow()
      {
         RestoreMaxSuggestions();
         if (editor != null && !editor.ReadOnly && !editor.IsPassWordProtected)
         {
            spellingForm.Visible = true;
         }
      }

      private void ShowCurrentWordWindow()
      {
         RestoreMaxSuggestions();
         //spelling.Text = editor.Text;
         //usefull when we should check only one word at cursor position.
         int wordIndex = GetWordIndex(editor.Text, editor.SelectionStart);
         //spelling.SpellCheck(wordIndex);
      }

      private void CallShowCurrentWordWindow(object sender, EventArgs e)
      {
         ShowCurrentWordWindow();
      }

      public Dictionary<int, int> CheckWordAtPosition()
      {
         Dictionary<int, int> result = new Dictionary<int, int>();
         Word word = GetWordFromPosition();
         if (!HunspellSpell(word))
         {
            result.Add(word.Start, word.Length);
         }
         return result;
      }

      public int GetWordIndex(string text, int position)
      {
         int result = 0;
         MatchCollection words = CalculateWords(text);
         result = words.Count - CalculateWords(text.Substring(position)).Count;
         return result;
      }

      public int GetTextIndexFromWordIndex(string text, int wordIndex)
      {
         MatchCollection words = CalculateWords(text);
         return words[wordIndex].Index;
      }

      //
      // Returns populated collection of matches.
      //
      private MatchCollection CalculateWords(string text)
      {
         return wordEx.Matches(text);
      }

      internal bool HasSpellingErrors()
      {
         return GetFirstMisspelledWord(editor.Text, 0).Text != "";
      }

      public Dictionary<int, int> GetMisspelledWordsRanges()
      {
         return GetMisspelledWordsRanges(0);
      }

      public Dictionary<int, int> GetMisspelledWordsRanges(int startIndex)
      {
         Dictionary<int, int> result = new Dictionary<int, int>();
         for (int i = startIndex; i < editor.Text.Length; i++)
         {
            Word word = Word.GetWordFromPosition(editor.Text, i);
            if (word.Text != "" && !HunspellSpell(word))
            {
               result.Add(word.Start, word.Length);
            }
            i += word.Length;
         }
         return result;
      }

      internal Word GetFirstMisspelledWord(string text, int startIndex)
      {
         Word result = new Word();
         for (int i = startIndex; i < editor.Text.Length; i++)
         {
            result = Word.GetWordFromPosition(text, i);
            if (result.Text != "" && !HunspellSpell(result) && !ignoreList.Contains(result.Text))
            {
               break;
            }
            result.Reset();
         }
         return result;
      }

      //
      // Used to get list containing range of each word. It is not used now but can still be helpful.
      //
      internal List<CharacterRange> GetAllWordsRanges(string p)
      {
         List<CharacterRange> result = new List<CharacterRange>();
         MatchCollection allWords = wordEx.Matches(p);
         foreach (Match m in allWords)
         {
            result.Add(new CharacterRange(m.Index, m.Length));
         }
         return result;
      }
   }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Ukraine Ukraine
"a man can do no more than he can" (proverb)
Don't you think there are a lot of things you couldn't do couple of years before but now you can? Things didn't change, but the only thing that changed is belief in yourself.

Comments and Discussions