diff --git a/src/Forms/Main.cs b/src/Forms/Main.cs index 1f08d13bb..7f26e860d 100644 --- a/src/Forms/Main.cs +++ b/src/Forms/Main.cs @@ -2766,8 +2766,56 @@ namespace Nikse.SubtitleEdit.Forms SpellCheck(true); } + private void SpellCheckViaWord() + { + WordSpellChecker wordSpellChecker = null; + int totalCorrections = 0; + try + { + wordSpellChecker = new WordSpellChecker(); + } + catch + { + MessageBox.Show(_language.UnableToStartWord); + //Configuration.Settings.General.SpellChecker = "hunspell"; ??? + return; + } + string version = wordSpellChecker.Version; + int index = 1; + foreach (Paragraph p in _subtitle.Paragraphs) + { + int errorsBefore; + int errorsAfter; + wordSpellChecker.NewDocument(); + ShowStatus(string.Format(_language.SpellChekingViaWordXLineYOfX, version, index, _subtitle.Paragraphs.Count.ToString())); + SubtitleListview1.SelectIndexAndEnsureVisible(index - 1); + string newText = wordSpellChecker.CheckSpelling(p.Text, out errorsBefore, out errorsAfter); + wordSpellChecker.CloseDocument(); + if (errorsAfter > 0) + { + wordSpellChecker.Quit(); + ShowStatus(string.Format(_language.SpellCheckAbortedXCorrections, totalCorrections)); + return; + } + else if (errorsBefore != errorsAfter) + { + textBoxListViewText.Text = newText; + } + totalCorrections += (errorsBefore - errorsAfter); + index++; + } + wordSpellChecker.Quit(); + ShowStatus(string.Format(_language.SpellCheckCompletedXCorrections, totalCorrections)); + } + private void SpellCheck(bool autoDetect) { + if (Configuration.Settings.General.SpellChecker.ToLower().Contains("word")) + { + SpellCheckViaWord(); + return; + } + try { string dictionaryFolder = Utilities.DictionaryFolder; diff --git a/src/Logic/Language.cs b/src/Logic/Language.cs index 993fba59b..ee905e49e 100644 --- a/src/Logic/Language.cs +++ b/src/Logic/Language.cs @@ -662,6 +662,10 @@ namespace Nikse.SubtitleEdit.Logic NetworkMode = "Networking mode", UserAndAction = "User/action", XStartedSessionYAtZ = "{0}: Started session {1} at {2}", + SpellChekingViaWordXLineYOfX = "Spell checking using Word {0} - line {1} / {2}", + UnableToStartWord = "Unable to start Microsoft Word", + SpellCheckAbortedXCorrections = "Spell check aborted. {0} corrections was made.", + SpellCheckCompletedXCorrections = "Spell check completed. {0} corrections was made.", Menu = new LanguageStructure.Main.MainMenu { diff --git a/src/Logic/LanguageStructure.cs b/src/Logic/LanguageStructure.cs index cde668424..a05e12e51 100644 --- a/src/Logic/LanguageStructure.cs +++ b/src/Logic/LanguageStructure.cs @@ -590,7 +590,11 @@ public string UserAndAction { get; set; } public string NetworkMode { get; set; } public string XStartedSessionYAtZ { get; set; } - + public string SpellChekingViaWordXLineYOfX { get; set; } + public string UnableToStartWord { get; set; } + public string SpellCheckAbortedXCorrections { get; set; } + public string SpellCheckCompletedXCorrections { get; set; } + public class MainMenu { public class FileMenu diff --git a/src/Logic/Settings.cs b/src/Logic/Settings.cs index c3f678931..fb6eac235 100644 --- a/src/Logic/Settings.cs +++ b/src/Logic/Settings.cs @@ -237,6 +237,7 @@ namespace Nikse.SubtitleEdit.Logic public bool AutoContinueOn { get; set; } public bool SyncListViewWithVideoWhilePlaying { get; set; } public int AutoBackupSeconds { get; set; } + public string SpellChecker { get; set; } public GeneralSettings() { @@ -283,6 +284,7 @@ namespace Nikse.SubtitleEdit.Logic AutoContinueOn = false; SyncListViewWithVideoWhilePlaying = false; AutoBackupSeconds = 0; + SpellChecker = "hunspell"; } } @@ -626,7 +628,10 @@ namespace Nikse.SubtitleEdit.Logic settings.General.AutoContinueOn = Convert.ToBoolean(subNode.InnerText); subNode = node.SelectSingleNode("AutoBackupSeconds"); if (subNode != null) - settings.General.AutoBackupSeconds = Convert.ToInt32(subNode.InnerText); + settings.General.AutoBackupSeconds = Convert.ToInt32(subNode.InnerText); + subNode = node.SelectSingleNode("SpellChecker"); + if (subNode != null) + settings.General.SpellChecker = subNode.InnerText; settings.Tools = new Nikse.SubtitleEdit.Logic.ToolsSettings(); node = doc.DocumentElement.SelectSingleNode("Tools"); @@ -929,8 +934,9 @@ namespace Nikse.SubtitleEdit.Logic textWriter.WriteElementString("DefaultAdjustMilliseconds", settings.General.DefaultAdjustMilliseconds.ToString()); textWriter.WriteElementString("AutoRepeatOn", settings.General.AutoRepeatOn.ToString()); textWriter.WriteElementString("AutoContinueOn", settings.General.AutoContinueOn.ToString()); - textWriter.WriteElementString("SyncListViewWithVideoWhilePlaying", settings.General.SyncListViewWithVideoWhilePlaying.ToString()); - textWriter.WriteElementString("AutoBackupSeconds", settings.General.AutoBackupSeconds.ToString()); + textWriter.WriteElementString("SyncListViewWithVideoWhilePlaying", settings.General.SyncListViewWithVideoWhilePlaying.ToString()); + textWriter.WriteElementString("AutoBackupSeconds", settings.General.AutoBackupSeconds.ToString()); + textWriter.WriteElementString("SpellChecker", settings.General.SpellChecker); textWriter.WriteEndElement(); textWriter.WriteStartElement("Tools", ""); diff --git a/src/Logic/WordLateBound.cs b/src/Logic/WordLateBound.cs new file mode 100644 index 000000000..0a9977912 --- /dev/null +++ b/src/Logic/WordLateBound.cs @@ -0,0 +1,102 @@ +using System; +using System.Reflection; + +namespace Nikse.SubtitleEdit.Logic +{ + /// + /// MS Word methods (late bound) for spell checking by Nikse + /// Mostly a bunch of hacks... + /// + internal class WordSpellChecker + { + private object _wordApplication; + private object _wordDocument; + private Type _wordApplicationType; + private Type _wordDocumentType; + + public WordSpellChecker() + { + _wordApplicationType = System.Type.GetTypeFromProgID("Word.Application"); + _wordApplication = Activator.CreateInstance(_wordApplicationType); + _wordApplicationType.InvokeMember("Top", BindingFlags.SetProperty, null, _wordApplication, new object[] { -1000 }); // hide window - it's a hack + _wordApplicationType.InvokeMember("Visible", BindingFlags.SetProperty, null, _wordApplication, new object[] { true }); // set visible to true - otherwise it will appear in the background + } + + public void NewDocument() + { + _wordDocumentType = System.Type.GetTypeFromProgID("Word.Document"); + _wordDocument = Activator.CreateInstance(_wordDocumentType); + } + + public void CloseDocument() + { + object saveChanges = false; + object p = Missing.Value; + _wordDocumentType.InvokeMember("Close", System.Reflection.BindingFlags.InvokeMethod, null, _wordDocument, new object[] { saveChanges, p, p }); + } + + public string Version + { + get + { + object wordVersion = _wordApplicationType.InvokeMember("Version", BindingFlags.GetProperty, null, _wordApplication, null); + return wordVersion.ToString(); + } + } + + public void Quit() + { + object saveChanges = false; + object originalFormat = Missing.Value; + object routeDocument = Missing.Value; + _wordApplicationType.InvokeMember("Quit", System.Reflection.BindingFlags.InvokeMethod, null, _wordApplication, new object[] { saveChanges, originalFormat, routeDocument }); + try + { + System.Runtime.InteropServices.Marshal.ReleaseComObject(_wordDocument); + System.Runtime.InteropServices.Marshal.ReleaseComObject(_wordApplication); + } + finally + { + _wordDocument = null; + _wordApplication = null; + } + } + + public string CheckSpelling(string text, out int errorsBefore, out int errorsAfter) + { + // insert text + object words = _wordDocumentType.InvokeMember("Words", BindingFlags.GetProperty, null, _wordDocument, null); + object range = words.GetType().InvokeMember("First", BindingFlags.GetProperty, null, words, null); + range.GetType().InvokeMember("InsertBefore", BindingFlags.InvokeMethod, null, range, new Object[] { text }); + + // spell check error count + object spellingErrors = _wordDocumentType.InvokeMember("SpellingErrors", BindingFlags.GetProperty, null, _wordDocument, null); + object spellingErrorsCount = spellingErrors.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, spellingErrors, null); + errorsBefore = int.Parse(spellingErrorsCount.ToString()); + System.Runtime.InteropServices.Marshal.ReleaseComObject(spellingErrors); + + // perform spell check + object p = Missing.Value; + _wordApplicationType.InvokeMember("Visible", BindingFlags.SetProperty, null, _wordApplication, new object[] { true }); // set visible to true - otherwise it will appear in the background + _wordDocumentType.InvokeMember("CheckSpelling", BindingFlags.InvokeMethod, null, _wordDocument, new Object[] { p, p, p, p, p, p, p, p, p, p, p, p }); // 12 parameters +// _wordApplicationType.InvokeMember("Top", BindingFlags.SetProperty, null, _wordApplication, new object[] { -1000 }); // hide window - it's a hack + + // spell check error count + spellingErrors = _wordDocumentType.InvokeMember("SpellingErrors", BindingFlags.GetProperty, null, _wordDocument, null); + spellingErrorsCount = spellingErrors.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, spellingErrors, null); + errorsAfter = int.Parse(spellingErrorsCount.ToString()); + System.Runtime.InteropServices.Marshal.ReleaseComObject(spellingErrors); + + // Get spellcheck text + object first = 0; + object characters = _wordDocumentType.InvokeMember("Characters", BindingFlags.GetProperty, null, _wordDocument, null); + object charactersCount = characters.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, characters, null); + object last = int.Parse(charactersCount.ToString()) - 1; + System.Runtime.InteropServices.Marshal.ReleaseComObject(characters); + + object resultText = range.GetType().InvokeMember("Text", BindingFlags.GetProperty, null, range, null); + return resultText.ToString().TrimEnd(); // result needs a trimming at the end + } + + } +} diff --git a/src/SubtitleEdit.csproj b/src/SubtitleEdit.csproj index 738da6ae1..32e9d47d2 100644 --- a/src/SubtitleEdit.csproj +++ b/src/SubtitleEdit.csproj @@ -561,6 +561,7 @@ +