From 70ed3115ee76bbf4b2839c64bee03e8faeda87e7 Mon Sep 17 00:00:00 2001 From: "scott%scott-macgregor.org" Date: Wed, 2 Feb 2005 19:42:55 +0000 Subject: [PATCH] Bug #278312 --> Initial framework for inline spell check sr=bienvenu git-svn-id: svn://10.0.0.236/trunk@168715 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/extensions/spellcheck/src/Makefile.in | 2 + .../spellcheck/src/mozInlineSpellChecker.cpp | 1208 +++++++++++++++++ .../spellcheck/src/mozInlineSpellChecker.h | 183 +++ .../spellcheck/src/mozSpellCheckerFactory.cpp | 10 +- 4 files changed, 1402 insertions(+), 1 deletion(-) create mode 100644 mozilla/extensions/spellcheck/src/mozInlineSpellChecker.cpp create mode 100644 mozilla/extensions/spellcheck/src/mozInlineSpellChecker.h diff --git a/mozilla/extensions/spellcheck/src/Makefile.in b/mozilla/extensions/spellcheck/src/Makefile.in index a06ad1e0bf3..e2f85039982 100644 --- a/mozilla/extensions/spellcheck/src/Makefile.in +++ b/mozilla/extensions/spellcheck/src/Makefile.in @@ -74,11 +74,13 @@ CPPSRCS = \ mozEnglishWordUtils.cpp \ mozGenericWordUtils.cpp \ mozSpellI18NManager.cpp \ + mozInlineSpellChecker.cpp \ $(NULL) EXTRA_DSO_LDOPTS = \ $(LIBS_DIR) \ $(MOZ_COMPONENT_LIBS) \ + $(MOZ_UNICHARUTIL_LIBS) \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.cpp b/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.cpp new file mode 100644 index 00000000000..1e470868376 --- /dev/null +++ b/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.cpp @@ -0,0 +1,1208 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Real-time Spellchecking + * + * The Initial Developer of the Original Code is Mozdev Group, Inc. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): Neil Deakin (neil@mozdevgroup.com) + * Scott MacGregor (mscott@mozilla.org) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsCOMPtr.h" + +#include "nsString.h" +#include "nsIServiceManager.h" +#include "nsIEnumerator.h" +#include "nsUnicharUtils.h" +#include "nsReadableUtils.h" + +#include "mozISpellI18NManager.h" +#include "mozInlineSpellChecker.h" + +#include "nsIPlaintextEditor.h" +#include "nsIDOMDocument.h" +#include "nsIDOMDocumentRange.h" +#include "nsIDOMNode.h" +#include "nsIDOMNSUIEvent.h" +#include "nsIDOMElement.h" +#include "nsIDOMText.h" +#include "nsIDOMNodeList.h" +#include "nsISelection.h" +#include "nsISelectionController.h" +#include "nsITextServicesDocument.h" +#include "nsITextServicesFilter.h" +#include "nsIDOMRange.h" +#include "nsIDOMNSRange.h" +#include "nsIDOMCharacterData.h" +#include "nsIDOMDocumentTraversal.h" +#include "nsIDOMNodeFilter.h" +#include "nsIDOMEventReceiver.h" +#include "nsIContent.h" +#include "nsIContentIterator.h" +#include "nsCRT.h" +#include "cattable.h" + +NS_INTERFACE_MAP_BEGIN(mozInlineSpellChecker) +NS_INTERFACE_MAP_ENTRY(nsIInlineSpellChecker) +NS_INTERFACE_MAP_ENTRY(nsIEditActionListener) +NS_INTERFACE_MAP_ENTRY(nsIDOMMouseListener) +NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMKeyListener) +NS_INTERFACE_MAP_ENTRY(nsIDOMKeyListener) +NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener, nsIDOMKeyListener) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(mozInlineSpellChecker) +NS_IMPL_RELEASE(mozInlineSpellChecker) + +mozInlineSpellChecker::mozInlineSpellChecker() +{ +} + +mozInlineSpellChecker::~mozInlineSpellChecker() +{ +} + +NS_IMETHODIMP +mozInlineSpellChecker::GetSpellChecker(nsIEditorSpellCheck **aSpellCheck) +{ + *aSpellCheck = mSpellCheck; + NS_IF_ADDREF(*aSpellCheck); + return NS_OK; +} + +NS_IMETHODIMP +mozInlineSpellChecker::Init(nsIEditor *aEditor) +{ + mEditor = do_GetWeakReference(aEditor); + return NS_OK; +} + +nsresult mozInlineSpellChecker::Cleanup() +{ + return UnregisterEventListeners(); +} + +// the inline spell checker listens to mouse events and keyboard navigation events +nsresult mozInlineSpellChecker::RegisterEventListeners() +{ + nsCOMPtr editor (do_QueryReferent(mEditor)); + NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); + + editor->AddEditActionListener(this); + + nsCOMPtr doc; + nsresult rv = editor->GetDocument(getter_AddRefs(doc)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr eventReceiver = do_QueryInterface(doc, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + eventReceiver->AddEventListenerByIID(NS_STATIC_CAST(nsIDOMMouseListener*, this), NS_GET_IID(nsIDOMMouseListener)); + eventReceiver->AddEventListenerByIID(NS_STATIC_CAST(nsIDOMKeyListener*, this), NS_GET_IID(nsIDOMKeyListener)); + + return NS_OK; +} + +nsresult mozInlineSpellChecker::UnregisterEventListeners() +{ + nsCOMPtr editor (do_QueryReferent(mEditor)); + NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); + + editor->RemoveEditActionListener(this); + + nsCOMPtr doc; + editor->GetDocument(getter_AddRefs(doc)); + NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER); + + nsCOMPtr eventReceiver = do_QueryInterface(doc); + NS_ENSURE_TRUE(eventReceiver, NS_ERROR_NULL_POINTER); + + eventReceiver->RemoveEventListenerByIID(NS_STATIC_CAST(nsIDOMMouseListener*, this), NS_GET_IID(nsIDOMMouseListener)); + eventReceiver->RemoveEventListenerByIID(NS_STATIC_CAST(nsIDOMKeyListener*, this), NS_GET_IID(nsIDOMKeyListener)); + + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::GetEnableRealTimeSpell(PRBool * aEnabled) +{ + NS_ENSURE_ARG_POINTER(aEnabled); + *aEnabled = mSpellCheck != nsnull; + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::SetEnableRealTimeSpell(PRBool aEnabled) +{ + nsresult res = NS_OK; + + if (aEnabled) { + if (!mSpellCheck) { + nsCOMPtr spellchecker = do_CreateInstance("@mozilla.org/editor/editorspellchecker;1", &res); + if (NS_SUCCEEDED(res) && spellchecker) + { + nsCOMPtr filter = do_CreateInstance("@mozilla.org/editor/txtsrvfilter;1", &res); + spellchecker->SetFilter(filter); + nsCOMPtr editor (do_QueryReferent(mEditor)); + res = spellchecker->InitSpellChecker(editor, PR_FALSE); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr tsDoc = do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &res); + NS_ENSURE_SUCCESS(res, res); + + res = tsDoc->SetFilter(filter); + NS_ENSURE_SUCCESS(res, res); + + res = tsDoc->InitWithEditor(editor); + NS_ENSURE_SUCCESS(res, res); + + mTextServicesDocument = tsDoc; + mSpellCheck = spellchecker; + + // spell checking is enabled, register our event listeners to track navigation + RegisterEventListeners(); + } + } + } + else + { + nsCOMPtr spellCheckSelection; + res = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); + NS_ENSURE_SUCCESS(res, res); + spellCheckSelection->RemoveAllRanges(); + UnregisterEventListeners(); + mSpellCheck = nsnull; + } + + return res; +} + +NS_IMETHODIMP mozInlineSpellChecker::SpellCheckAfterEditorChange(PRInt32 action, nsISelection *aSelection, + nsIDOMNode *previousSelectedNode, PRInt32 previousSelectedOffset, + nsIDOMNode *aStartNode, PRInt32 aStartOffset, nsIDOMNode *aEndNode, + PRInt32 aEndOffset) +{ + NS_ENSURE_ARG_POINTER(aSelection); + nsCOMPtr editor (do_QueryReferent(mEditor)); + if (!mSpellCheck || !editor) + return NS_ERROR_NOT_INITIALIZED; + + nsCOMPtr anchorNode; + nsresult res = aSelection->GetAnchorNode(getter_AddRefs(anchorNode)); + NS_ENSURE_SUCCESS(res, res); + + PRInt32 anchorOffset; + res = aSelection->GetAnchorOffset(&anchorOffset); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr spellCheckSelection; + res = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); + NS_ENSURE_SUCCESS(res, res); + + CleanupRangesInSelection(spellCheckSelection); + + switch (action) + { + case kOpInsertBreak: + { + // if we just inserted a line break, we need to finish spell checking the last word on the previous + // line since in this case the line break is really a end of word terminator... + res = AdjustSpellHighlighting(previousSelectedNode, previousSelectedOffset, spellCheckSelection, PR_FALSE); + break; + } + + case kOpMakeList: + case kOpIndent: + case kOpOutdent: + case kOpAlign: + case kOpRemoveList: + case kOpMakeDefListItem: + res = SpellCheckBetweenNodes(aStartNode, aStartOffset, aEndNode, aEndOffset, spellCheckSelection); + break; + + case kOpInsertText: + case kOpInsertIMEText: + { + PRInt32 offset = previousSelectedOffset; + + if (anchorNode == previousSelectedNode) + { + if (offset == anchorOffset) + offset--; + res = AdjustSpellHighlighting(anchorNode, offset, spellCheckSelection, PR_FALSE); + } + else + res = SpellCheckBetweenNodes(aStartNode, aStartOffset, anchorNode, offset, spellCheckSelection); + break; + } + + case kOpHTMLPaste: + // spell check the inserted nodes caused by the space. + res = SpellCheckBetweenNodes(aStartNode, aStartOffset, aEndNode, aEndOffset, spellCheckSelection); + break; + + case kOpDeleteSelection: + { + // this is what happens when the delete or backspace key is pressed + PRInt32 offset = (anchorOffset > 0) ? (anchorOffset - 1) : anchorOffset; + res = AdjustSpellHighlighting(anchorNode, offset, spellCheckSelection, PR_TRUE); + break; + } + + case kOpInsertQuotation: + // spell check the text at the previous caret position, but no need to + // check the quoted text itself. + res = AdjustSpellHighlighting(previousSelectedNode, previousSelectedOffset - 1, + spellCheckSelection, PR_FALSE); + break; + + case kOpRemoveTextProperty: + case kOpResetTextProperties: + res = SpellCheckSelection(aSelection, spellCheckSelection); + break; + + case kOpMakeBasicBlock: + // this doesn't adjust the selection, nor add or insert any text, so + // no words need to be spell-checked + res = SpellCheckBetweenNodes(aStartNode,aStartOffset,aEndNode,aEndOffset, spellCheckSelection); + break; + + case kOpLoadHTML: + // XXX this should cause a spellcheck of the entire document + break; + + case kOpInsertElement: + PRBool iscollapsed; + aSelection->GetIsCollapsed(&iscollapsed); + if (iscollapsed) + res = AdjustSpellHighlighting(anchorNode, anchorOffset, spellCheckSelection, PR_FALSE); + else + res = SpellCheckSelection(aSelection, spellCheckSelection); + break; + + case kOpSetTextProperty: + res = SpellCheckSelection(aSelection,spellCheckSelection); + break; + + case kOpUndo: + case kOpRedo: + // XXX handling these cases is quite hard -- this isn't quite right + if (anchorNode == previousSelectedNode) + res = SpellCheckBetweenNodes(anchorNode, PR_MIN(anchorOffset, previousSelectedOffset), + anchorNode, PR_MAX(anchorOffset, previousSelectedOffset), spellCheckSelection); + else + { + nsCOMPtr rootElem; + res = editor->GetRootElement(getter_AddRefs(rootElem)); + NS_ENSURE_SUCCESS(res, res); + res = SpellCheckBetweenNodes(rootElem, 0, rootElem, -1, spellCheckSelection); + } + break; + } + + // remember the current cursor position after every change.. + SaveCurrentSelectionPosition(); + return res; +} + +NS_IMETHODIMP mozInlineSpellChecker::SpellCheckRange(nsIDOMRange *aRange) +{ + NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr spellCheckSelection; + nsresult rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); + NS_ENSURE_SUCCESS(rv, rv); + + CleanupRangesInSelection(spellCheckSelection); + + return SpellCheckRange(aRange,spellCheckSelection); +} + +NS_IMETHODIMP mozInlineSpellChecker::GetMispelledWord(nsIDOMNode *aNode, PRInt32 aOffset, nsIDOMRange **newword) +{ + nsCOMPtr spellCheckSelection; + nsresult res = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); + NS_ENSURE_SUCCESS(res, res); + + return IsPointInSelection(spellCheckSelection, aNode, aOffset, newword); +} + +NS_IMETHODIMP mozInlineSpellChecker::ReplaceWord(nsIDOMNode *aNode, PRInt32 aOffset, const nsAString &newword) +{ + nsCOMPtr editor (do_QueryReferent(mEditor)); + NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); + NS_ENSURE_TRUE(newword.Length() != 0, NS_ERROR_FAILURE); + + nsCOMPtr range; + nsresult res = GetMispelledWord(aNode, aOffset, getter_AddRefs(range)); + NS_ENSURE_SUCCESS(res, res); + + if (range) + { + range->DeleteContents(); + + nsCOMPtr selection; + res = editor->GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr container; + res = range->GetStartContainer(getter_AddRefs(container)); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr chars = do_QueryInterface(container); + if (chars) + { + PRInt32 offset; + res = range->GetStartOffset(&offset); + NS_ENSURE_SUCCESS(res, res); + + chars->InsertData(offset,newword); + selection->Collapse(container, offset + newword.Length()); + } + else + { + nsCOMPtr doc; + res = editor->GetDocument(getter_AddRefs(doc)); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr newtext; + res = doc->CreateTextNode(newword,getter_AddRefs(newtext)); + NS_ENSURE_SUCCESS(res, res); + + res = range->InsertNode(newtext); + NS_ENSURE_SUCCESS(res, res); + + selection->Collapse(newtext, newword.Length()); + } + } + + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::AddWordToDictionary(const nsAString &word) +{ + NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); + + nsAutoString wordstr(word); + nsresult res = mSpellCheck->AddWordToDictionary(wordstr.get()); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr editor (do_QueryReferent(mEditor)); + NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); + + nsCOMPtr rootElem; + res = editor->GetRootElement(getter_AddRefs(rootElem)); + NS_ENSURE_SUCCESS(res, res); + + return SpellCheckBetweenNodes(rootElem, 0, rootElem, -1, NULL); +} + +NS_IMETHODIMP mozInlineSpellChecker::IgnoreWord(const nsAString &word) +{ + NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); + + nsAutoString wordstr(word); + nsresult res = mSpellCheck->IgnoreWordAllOccurrences(wordstr.get()); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr editor (do_QueryReferent(mEditor)); + NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); + + nsCOMPtr rootElem; + res = editor->GetRootElement(getter_AddRefs(rootElem)); + NS_ENSURE_SUCCESS(res, res); + + return SpellCheckBetweenNodes(rootElem, 0, rootElem, -1, NULL); +} + +NS_IMETHODIMP mozInlineSpellChecker::WillCreateNode(const nsAString & aTag, nsIDOMNode *aParent, PRInt32 aPosition) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::DidCreateNode(const nsAString & aTag, nsIDOMNode *aNode, nsIDOMNode *aParent, + PRInt32 aPosition, nsresult aResult) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::WillInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, + PRInt32 aPosition) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::DidInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, + PRInt32 aPosition, nsresult aResult) +{ + + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::WillDeleteNode(nsIDOMNode *aChild) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::WillSplitNode(nsIDOMNode *aExistingRightNode, PRInt32 aOffset) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::DidSplitNode(nsIDOMNode *aExistingRightNode, PRInt32 aOffset, + nsIDOMNode *aNewLeftNode, nsresult aResult) +{ + return SpellCheckBetweenNodes(aNewLeftNode, 0, aNewLeftNode, 0, NULL); +} + +NS_IMETHODIMP mozInlineSpellChecker::WillJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::DidJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, + nsIDOMNode *aParent, nsresult aResult) +{ + return SpellCheckBetweenNodes(aRightNode, 0, aRightNode, 0, NULL); +} + +NS_IMETHODIMP mozInlineSpellChecker::WillInsertText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, const nsAString & aString) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::DidInsertText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, + const nsAString & aString, nsresult aResult) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::WillDeleteText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, PRInt32 aLength) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::DidDeleteText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, PRInt32 aLength, nsresult aResult) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::WillDeleteSelection(nsISelection *aSelection) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::DidDeleteSelection(nsISelection *aSelection) +{ + return NS_OK; +} + +nsresult +mozInlineSpellChecker::SpellCheckSelection(nsISelection *aSelection, nsISelection *aSpellCheckSelection) +{ + NS_ENSURE_ARG_POINTER(aSelection); + if (!mSpellCheck) + return NS_ERROR_NOT_INITIALIZED; + + PRInt32 count; + aSelection->GetRangeCount(&count); + + for (PRInt32 index =0; index < count; index++) + { + nsCOMPtr checkRange; + aSelection->GetRangeAt(index, getter_AddRefs(checkRange)); + + if (checkRange) + { + nsCOMPtr startNode; + nsCOMPtr endNode; + PRInt32 startOffset, endOffset; + + checkRange->GetStartContainer(getter_AddRefs(startNode)); + checkRange->GetEndContainer(getter_AddRefs(endNode)); + checkRange->GetStartOffset(&startOffset); + checkRange->GetEndOffset(&endOffset); + + return SpellCheckBetweenNodes(startNode, startOffset, endNode, endOffset, aSpellCheckSelection); + } + } + + return NS_OK; +} + +nsresult +mozInlineSpellChecker::SpellCheckBetweenNodes(nsIDOMNode *aStartNode, + PRInt32 aStartOffset, + nsIDOMNode *aEndNode, + PRInt32 aEndOffset, + nsISelection *aSpellCheckSelection) +{ + nsresult res; + nsCOMPtr spellCheckSelection = aSpellCheckSelection; + nsCOMPtr editor (do_QueryReferent(mEditor)); + NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); + + if (!spellCheckSelection) + { + res = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); + NS_ENSURE_SUCCESS(res, res); + } + + nsCOMPtr doc; + res = editor->GetDocument(getter_AddRefs(doc)); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr docrange = do_QueryInterface(doc); + NS_ENSURE_TRUE(docrange, NS_ERROR_FAILURE); + + nsCOMPtr range; + res = docrange->CreateRange(getter_AddRefs(range)); + NS_ENSURE_SUCCESS(res, res); + + if (aEndOffset == -1) + { + nsCOMPtr childNodes; + res = aEndNode->GetChildNodes(getter_AddRefs(childNodes)); + NS_ENSURE_SUCCESS(res, res); + + PRUint32 childCount; + res = childNodes->GetLength(&childCount); + NS_ENSURE_SUCCESS(res, res); + + aEndOffset = childCount; + } + + range->SetStart(aStartNode,aStartOffset); + + if (aEndOffset) + range->SetEnd(aEndNode, aEndOffset); + else + range->SetEndAfter(aEndNode); + + // HACK: try to avoid an assertion later on in the code when we iterate + // over a range with only one character in it. if the range is really only one + // character, bail out early.. + nsCOMPtr startNode; + nsCOMPtr endNode; + PRInt32 startOffset; + PRInt32 endOffset; + range->GetStartContainer(getter_AddRefs(startNode)); + range->GetStartOffset(&startOffset); + range->GetEndContainer(getter_AddRefs(endNode)); + range->GetEndOffset(&endOffset); + + if (startNode == endNode && startOffset == endOffset) + return NS_OK; // don't call adjust spell highlighting for a single character + + return SpellCheckRange(range,spellCheckSelection); +} + +static inline PRBool IsNonwordChar(PRUnichar chr) +{ + // a non-word character is one that can end a word, such as whitespace or + // most punctuation. + // mscott: We probably need to modify this to make it work for non ascii based languages... + // but then again our spell checker doesn't support multi byte languages anyway. + return ((chr != '\'') && (GetCat(chr) != 5)); +} + +// There are certain conditions when we don't want to spell check a node. In particular +// quotations, moz signatures, etc. This routine returns false for these cases. +nsresult mozInlineSpellChecker::CheckShouldSpellCheck(nsIDOMNode *aNode, PRBool *checkSpelling) +{ + *checkSpelling = PR_TRUE; + NS_ENSURE_ARG_POINTER(aNode); + + nsCOMPtr editor (do_QueryReferent(mEditor)); + NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); + + PRUint32 flags; + editor->GetFlags(&flags); + if (flags & nsIPlaintextEditor::eEditorMailMask) + { + nsCOMPtr parent; + aNode->GetParentNode(getter_AddRefs(parent)); + + while (parent) + { + nsCOMPtr parentElement = do_QueryInterface(parent); + if (!parentElement) + break; + + nsAutoString parentTagName; + parentElement->GetTagName(parentTagName); + + if (parentTagName.Equals(NS_LITERAL_STRING("blockquote"), nsCaseInsensitiveStringComparator())) + { + *checkSpelling = PR_FALSE; + break; + } + else if (parentTagName.Equals(NS_LITERAL_STRING("pre"), nsCaseInsensitiveStringComparator())) + { + nsAutoString classname; + parentElement->GetAttribute(NS_LITERAL_STRING("class"),classname); + if (classname.Equals(NS_LITERAL_STRING("moz-signature"))) + *checkSpelling = PR_FALSE; + } + + nsCOMPtr nextParent; + parent->GetParentNode(getter_AddRefs(nextParent)); + parent = nextParent; + } + } + + return NS_OK; +} + +// takes a point in a text node and generates a range for the word containing that point. Note: +// aWordRange will be NULL if we don't have a word. +nsresult mozInlineSpellChecker::GenerateRangeForSurroundingWord(nsIDOMNode * aNode, PRInt32 aOffset, nsIDOMRange ** aWordRange) +{ + NS_ENSURE_ARG_POINTER(aNode); + + PRUint32 textLength = 0; + const PRUnichar *textChars = nsnull; + nsAutoString text; + nsresult rv = aNode->GetNodeValue(text); + NS_ENSURE_SUCCESS(rv, rv); + + textChars = text.get(); + textLength = text.Length(); + if (aOffset == -1 || aOffset >= textLength) + aOffset = textLength - 1; + + PRInt32 currentOffset = aOffset; + if (currentOffset && textChars[currentOffset] == ' ') + currentOffset--; + + // back up our offset into the text node until we hit the beginning of the text OR + // we find white space (NOT punctuation) + while (currentOffset && textChars[currentOffset] != ' ') + currentOffset--; + + PRInt32 lastWordBeginPosition = -1; + PRInt32 lastWordEndPos = -1; + PRInt32 begin; + PRInt32 end; + + do + { + rv = mConverter->FindNextWord(textChars, textLength, currentOffset, &begin, &end); + if (NS_SUCCEEDED(rv) && (begin != -1)) + { + const nsAString &word = Substring(text, begin, end - begin); + + lastWordBeginPosition = begin; + lastWordEndPos = end; + currentOffset = lastWordEndPos; + } + } while (begin != -1 && currentOffset < aOffset); + + // only return the range if our original point in the string is inside the word + if (aOffset >= lastWordBeginPosition && aOffset <= lastWordEndPos) + { + nsCOMPtr editor (do_QueryReferent(mEditor)); + NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); + + nsCOMPtr doc; + rv = editor->GetDocument(getter_AddRefs(doc)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr docrange = do_QueryInterface(doc); + nsCOMPtr range; + rv = docrange->CreateRange(aWordRange); + NS_ENSURE_SUCCESS(rv, rv); + + (*aWordRange)->SetStart(aNode, lastWordBeginPosition); + (*aWordRange)->SetEnd(aNode, lastWordEndPos); + } + else + *aWordRange = nsnull; + + return NS_OK; +} + +nsresult +mozInlineSpellChecker::AdjustSpellHighlighting(nsIDOMNode *aNode, + PRInt32 aOffset, + nsISelection *aSpellCheckSelection, + PRBool isDeletion) +{ + NS_ENSURE_TRUE(aNode, NS_OK); + nsCOMPtr currentNode = aNode; + nsresult rv = NS_OK; + do + { + PRUint16 nodeType; + rv = currentNode->GetNodeType(&nodeType); + NS_ENSURE_SUCCESS(rv, rv); + + if (nodeType == nsIDOMNode::TEXT_NODE) + break; + + nsCOMPtr childNodes; + rv = currentNode->GetChildNodes(getter_AddRefs(childNodes)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr child; + rv = childNodes->Item(aOffset, getter_AddRefs(child)); + NS_ENSURE_SUCCESS(rv, rv); + + currentNode = child; + aOffset = 0; + } while (currentNode); + + // make sure we found a text node + if (!currentNode) + return NS_OK; + + // HACK: special case deletion when we had to walk up the dom tree to find the previous text node. + // Reset the offset to be the end of this previous text node instead of the beginning + if (isDeletion && currentNode != aNode) + { + nsAutoString text; + rv = currentNode->GetNodeValue(text); + if (text.Length()) + aOffset = text.Length() - 1; + } + + nsCOMPtr wordRange; + rv = GenerateRangeForSurroundingWord(currentNode, aOffset, getter_AddRefs(wordRange)); + + // if we don't have a word range to examine, then bail out early. + if (!wordRange) + return NS_OK; + + // if the user just started typing inside of an existing word, remove that word from the spell check + // selection list (if it was even there) + if (!EndOfAWord(currentNode, aOffset) && !isDeletion) + RemoveCurrentWordFromSpellSelection(aSpellCheckSelection, wordRange); + + // if we aren't at the end of a word then don't kick off inline spell checking + // if we are processing a delete character, proceed so we can clear the spell check underline of the + // current word. + // Extra logic: if we are handling a deletion and the current character (after the delete) is a end of word + // delimter (like a space) then don't clear the underline selection of the word. This fixes the case where + // you type: "helllo world". Put the cursor to the left of 'w' and hit back space, as long as there is white + // space between the cursor and the last character in the previous word (helllo) we shouldn't clear the highlight + // selection on helllo. + if ((!EndOfAWord(currentNode, aOffset) && !isDeletion) || (isDeletion && EndOfAWord(currentNode, aOffset))) + return NS_OK; + + PRBool checkSpelling; + CheckShouldSpellCheck(currentNode, &checkSpelling); + if (!checkSpelling) + return NS_OK; + + rv = RemoveCurrentWordFromSpellSelection(aSpellCheckSelection, wordRange); + + // for the case of deletion, if the cursor is inside of a spell check selection (i.e. + // the user typed some text then a word terminator and hit backspace to fix the spelling) + // we should clear the red underline from that word since they are now editing it again. + if (isDeletion) + return NS_OK; + + // expand the current selection point to a word range + PRBool isMisspelled = PR_FALSE; + nsAutoString word; + wordRange->ToString(word); + + // empty words should be skipped + if (word.IsEmpty()) + return NS_OK; + + rv = mSpellCheck->CheckCurrentWordNoSuggest(word.get(),&isMisspelled); + NS_ENSURE_SUCCESS(rv, rv); + + if (isMisspelled) + aSpellCheckSelection->AddRange(wordRange); + + return NS_OK; +} + +// SpellCheckRange --> Looks for words that span aRange and spell checks each individual word. +nsresult mozInlineSpellChecker::SpellCheckRange(nsIDOMRange *aRange, nsISelection *aSpellCheckSelection) +{ + nsCOMPtr selectionRange; + nsresult res = aRange->CloneRange(getter_AddRefs(selectionRange)); + NS_ENSURE_SUCCESS(res, res); + + PRBool iscollapsed; + res = aRange->GetCollapsed(&iscollapsed); + NS_ENSURE_SUCCESS(res, res); + + res = mTextServicesDocument->SetExtent(selectionRange); + NS_ENSURE_SUCCESS(res, res); + + PRBool done, isMisspelled; + PRInt32 begin, end, startOffset, endOffset; + PRUint32 selOffset = 0; + nsCOMPtr startNode; + nsCOMPtr endNode; + + if (!mConverter) + { + nsCOMPtr manager(do_GetService("@mozilla.org/spellchecker/i18nmanager;1", &res)); + if (manager && NS_SUCCEEDED(res)) + { + nsXPIDLString language; + res = manager->GetUtil(language.get(),getter_AddRefs(mConverter)); + } + } + + while (NS_SUCCEEDED(mTextServicesDocument->IsDone(&done)) && !done) + { + nsAutoString textblock; + res = mTextServicesDocument->GetCurrentTextBlock(&textblock); + NS_ENSURE_SUCCESS(res, res); + + do + { + const PRUnichar *textChars = textblock.get(); + PRInt32 textLength = textblock.Length(); + + res = mConverter->FindNextWord(textChars, textLength, selOffset, &begin, &end); + if (NS_SUCCEEDED(res) && (begin != -1)) + { + const nsAString &word = Substring(textblock, begin, end - begin); + res = mSpellCheck->CheckCurrentWordNoSuggest(PromiseFlatString(word).get(), &isMisspelled); + + nsCOMPtr wordrange; + res = mTextServicesDocument->GetDOMRangeFor(begin, end - begin, getter_AddRefs(wordrange)); + + wordrange->GetStartContainer(getter_AddRefs(startNode)); + wordrange->GetEndContainer(getter_AddRefs(endNode)); + wordrange->GetStartOffset(&startOffset); + wordrange->GetEndOffset(&endOffset); + + PRBool checkSpelling; + CheckShouldSpellCheck(startNode, &checkSpelling); + if (!checkSpelling) + break; + + nsCOMPtr currentRange; + IsPointInSelection(aSpellCheckSelection, startNode, startOffset, + getter_AddRefs(currentRange)); + if (!currentRange) + IsPointInSelection(aSpellCheckSelection, endNode, endOffset - 1, getter_AddRefs(currentRange)); + + if (currentRange) // remove the old range first + aSpellCheckSelection->RemoveRange(currentRange); + if (isMisspelled) + aSpellCheckSelection->AddRange(wordrange); + } + selOffset = end; + } while (end != -1); + + mTextServicesDocument->NextBlock(); + selOffset = 0; + } + + return NS_OK; +} + +PRBool mozInlineSpellChecker::EndOfAWord(nsIDOMNode *aNode, PRInt32 aOffset) +{ + PRBool endOfWord = PR_FALSE; + nsAutoString text; + PRUint16 nodeType; + + if (aNode) + { + nsresult res = aNode->GetNodeType(&nodeType); + if (NS_SUCCEEDED(res)) + { + if (nodeType == nsIDOMNode::TEXT_NODE) { + res = aNode->GetNodeValue(text); + if (NS_SUCCEEDED(res) && IsNonwordChar(text[aOffset])) + endOfWord = PR_TRUE; + } + } + } + + return endOfWord; +} + +nsresult +mozInlineSpellChecker::IsPointInSelection(nsISelection *aSelection, + nsIDOMNode *aNode, + PRInt32 aOffset, + nsIDOMRange **aRange) +{ + *aRange = NULL; + NS_ENSURE_ARG_POINTER(aNode); + NS_ENSURE_ARG_POINTER(aSelection); + + PRInt32 count; + aSelection->GetRangeCount(&count); + + PRInt32 t; + for (t=0; t checkRange; + aSelection->GetRangeAt(t,getter_AddRefs(checkRange)); + nsCOMPtr nsCheckRange = do_QueryInterface(checkRange); + + PRInt32 start, end; + checkRange->GetStartOffset(&start); + checkRange->GetEndOffset(&end); + + PRBool isInRange; + nsCheckRange->IsPointInRange(aNode,aOffset,&isInRange); + if (isInRange) + { + *aRange = checkRange; + NS_ADDREF(*aRange); + break; + } + } + + return NS_OK; +} + +nsresult +mozInlineSpellChecker::CleanupRangesInSelection(nsISelection *aSelection) +{ + // integrity check - remove ranges that have collapsed to nothing. This + // can happen if the node containing a highlighted word was removed. + NS_ENSURE_ARG_POINTER(aSelection); + + PRInt32 count; + aSelection->GetRangeCount(&count); + + for (PRInt32 index = 0; index < count; index++) + { + nsCOMPtr checkRange; + aSelection->GetRangeAt(index, getter_AddRefs(checkRange)); + + if (checkRange) + { + PRBool collapsed; + checkRange->GetCollapsed(&collapsed); + if (collapsed) + { + aSelection->RemoveRange(checkRange); + index--; + count--; + } + } + } + + return NS_OK; +} + +// a helper routine that expands the current position in the selection to the surrounding word +// and then removes it from the spell checker selection +nsresult mozInlineSpellChecker::RemoveCurrentWordFromSpellSelection(nsISelection *aSpellCheckSelection, nsIDOMRange * aWordRange) +{ + nsresult rv = NS_OK; + NS_ENSURE_ARG_POINTER(aSpellCheckSelection); + NS_ENSURE_ARG_POINTER(aWordRange); + + nsCOMPtr startNode; + nsCOMPtr endNode; + PRInt32 startOffset, endOffset; + + aWordRange->GetStartContainer(getter_AddRefs(startNode)); + aWordRange->GetEndContainer(getter_AddRefs(endNode)); + aWordRange->GetStartOffset(&startOffset); + aWordRange->GetEndOffset(&endOffset); + + nsCOMPtr currentRange; + + IsPointInSelection(aSpellCheckSelection, startNode, startOffset, getter_AddRefs(currentRange)); + if (currentRange) + aSpellCheckSelection->RemoveRange(currentRange); + + IsPointInSelection(aSpellCheckSelection, endNode, endOffset - 1, getter_AddRefs(currentRange)); + if (currentRange) + aSpellCheckSelection->RemoveRange(currentRange); + + return NS_OK; +} + +nsresult mozInlineSpellChecker::GetSpellCheckSelection(nsISelection ** aSpellCheckSelection) +{ + nsCOMPtr editor (do_QueryReferent(mEditor)); + NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); + + nsCOMPtr selcon; + nsresult rv = editor->GetSelectionController(getter_AddRefs(selcon)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr spellCheckSelection; + return selcon->GetSelection(nsISelectionController::SELECTION_SPELLCHECK, aSpellCheckSelection); +} + +nsresult mozInlineSpellChecker::SaveCurrentSelectionPosition() +{ + nsCOMPtr editor (do_QueryReferent(mEditor)); + NS_ENSURE_TRUE(editor, NS_OK); + + // figure out the old cursor position based on the current selection + nsCOMPtr selection; + nsresult rv = editor->GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = selection->GetFocusNode(getter_AddRefs(mCurrentSelectionAnchorNode)); + NS_ENSURE_SUCCESS(rv, rv); + + selection->GetFocusOffset(&mCurrentSelectionOffset); + + return NS_OK; +} + +// HandleNavigationEvent: acts upon mouse clicks and keyboard navigation changes, +// spell checking the previous word if the new navigation location movees us to another word. +// This is complicated by the fact that our mouse events are happening after selection has been +// changed to account for the mouse click. But keyboard events are happening before the caret +// selection has changed. Working around this by letting keyboard events setting forceWordSpellCheck +// to true. aNewPositionOffset also trys to work around this for the DOM_VK_RIGHT and DOM_VK_LEFT cases. +nsresult mozInlineSpellChecker::HandleNavigationEvent(nsIDOMEvent * aEvent, PRBool aForceWordSpellCheck, PRInt32 aNewPositionOffset) +{ + // get the current selection and compare it to the new selection. + nsresult rv; + + nsCOMPtr currentAnchorNode = mCurrentSelectionAnchorNode; + PRInt32 currentAnchorOffset = mCurrentSelectionOffset; + + // now remember the new focus position resulting from the event + SaveCurrentSelectionPosition(); + + NS_ENSURE_TRUE(currentAnchorNode, NS_OK); + + // expand the old selection into a range for the nearest word boundary + nsCOMPtr currentWordRange; + GenerateRangeForSurroundingWord(currentAnchorNode, currentAnchorOffset, getter_AddRefs(currentWordRange)); + + if (!currentWordRange) + return NS_OK; + + nsAutoString word; + currentWordRange->ToString(word); + + nsCOMPtr currentWordNSRange = do_QueryInterface(currentWordRange, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + PRBool isInRange; + rv = currentWordNSRange->IsPointInRange(mCurrentSelectionAnchorNode, mCurrentSelectionOffset + aNewPositionOffset, &isInRange); + NS_ENSURE_SUCCESS(rv, rv); + + if (!isInRange || aForceWordSpellCheck) // selection is moving to a new word, spell check the current word + { + nsCOMPtr spellCheckSelection; + GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); + SpellCheckRange(currentWordRange, spellCheckSelection); + } + + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::HandleEvent(nsIDOMEvent* aEvent) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::MouseClick(nsIDOMEvent *aMouseEvent) +{ + // ignore any errors from HandleNavigationEvent as we don't want to prevent + // anyone else from seeing this event. + HandleNavigationEvent(aMouseEvent, PR_FALSE); + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::MouseDown(nsIDOMEvent* aMouseEvent) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::MouseUp(nsIDOMEvent* aMouseEvent) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::MouseDblClick(nsIDOMEvent* aMouseEvent) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::MouseOver(nsIDOMEvent* aMouseEvent) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::MouseOut(nsIDOMEvent* aMouseEvent) +{ + return NS_OK; +} + +NS_IMETHODIMP mozInlineSpellChecker::KeyDown(nsIDOMEvent* aKeyEvent) +{ + return NS_OK; +} + + +NS_IMETHODIMP mozInlineSpellChecker::KeyUp(nsIDOMEvent* aKeyEvent) +{ + return NS_OK; +} + + +NS_IMETHODIMP mozInlineSpellChecker::KeyPress(nsIDOMEvent* aKeyEvent) +{ + nsCOMPtrkeyEvent = do_QueryInterface(aKeyEvent); + NS_ENSURE_TRUE(keyEvent, NS_OK); + + PRUint32 keyCode; + keyEvent->GetKeyCode(&keyCode); + + // we only care about navigation keys that moved selection + switch (keyCode) + { + case nsIDOMKeyEvent::DOM_VK_RIGHT: + case nsIDOMKeyEvent::DOM_VK_LEFT: + HandleNavigationEvent(aKeyEvent, PR_FALSE, keyCode == nsIDOMKeyEvent::DOM_VK_RIGHT ? 1 : -1); + break; + case nsIDOMKeyEvent::DOM_VK_UP: + case nsIDOMKeyEvent::DOM_VK_DOWN: + case nsIDOMKeyEvent::DOM_VK_HOME: + case nsIDOMKeyEvent::DOM_VK_END: + case nsIDOMKeyEvent::DOM_VK_PAGE_UP: + case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN: + HandleNavigationEvent(aKeyEvent, PR_TRUE /* force a spelling correction */); + break; + } + + return NS_OK; +} diff --git a/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.h b/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.h new file mode 100644 index 00000000000..888db647d81 --- /dev/null +++ b/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.h @@ -0,0 +1,183 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Inline Spellchecker + * + * The Initial Developer of the Original Code is Mozdev Group, Inc. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): Neil Deakin (neil@mozdevgroup.com) + * Scott MacGregor (mscott@mozilla.org) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef __mozinlinespellchecker_h__ +#define __mozinlinespellchecker_h__ + +#include "nsIEditorSpellCheck.h" +#include "nsIEditActionListener.h" +#include "nsIInlineSpellChecker.h" +#include "nsITextServicesDocument.h" +#include "nsIDOMTreeWalker.h" +#include "nsWeakReference.h" +#include "nsIEditor.h" +#include "nsIDOMMouseListener.h" +#include "nsIDOMKeyListener.h" +#include "nsWeakReference.h" + +class nsIDOMMouseEventListener; + +class mozInlineSpellChecker : public nsIInlineSpellChecker, nsIEditActionListener, nsIDOMMouseListener, nsIDOMKeyListener, + nsSupportsWeakReference +{ +private: + + nsWeakPtr mEditor; + nsCOMPtr mSpellCheck; + nsCOMPtr mTextServicesDocument; + nsCOMPtr mTreeWalker; + nsCOMPtr mConverter; + + // we need to keep track of the current text position in the document + // so we can spell check the old word when the user clicks around the document. + nsCOMPtr mCurrentSelectionAnchorNode; + PRInt32 mCurrentSelectionOffset; + + // TODO: these should be defined somewhere so that they don't have to be copied + // from editor! + enum OperationID + { + kOpIgnore = -1, + kOpNone = 0, + kOpUndo, + kOpRedo, + kOpInsertNode, + kOpCreateNode, + kOpDeleteNode, + kOpSplitNode, + kOpJoinNode, + kOpDeleteSelection, + + kOpInsertBreak = 1000, + kOpInsertText = 1001, + kOpInsertIMEText = 1002, + kOpDeleteText = 1003, + + kOpMakeList = 3001, + kOpIndent = 3002, + kOpOutdent = 3003, + kOpAlign = 3004, + kOpMakeBasicBlock = 3005, + kOpRemoveList = 3006, + kOpMakeDefListItem = 3007, + kOpInsertElement = 3008, + kOpInsertQuotation = 3009, + kOpSetTextProperty = 3010, + kOpRemoveTextProperty = 3011, + kOpHTMLPaste = 3012, + kOpLoadHTML = 3013, + kOpResetTextProperties = 3014, + kOpSetAbsolutePosition = 3015, + kOpRemoveAbsolutePosition = 3016, + kOpDecreaseZIndex = 3017, + kOpIncreaseZIndex = 3018 + }; + +public: + + NS_DECL_ISUPPORTS + NS_DECL_NSIEDITACTIONLISTENER + NS_DECL_NSIINLINESPELLCHECKER + + /*BEGIN implementations of mouseevent handler interface*/ + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent); + NS_IMETHOD MouseDown(nsIDOMEvent* aMouseEvent); + NS_IMETHOD MouseUp(nsIDOMEvent* aMouseEvent); + NS_IMETHOD MouseClick(nsIDOMEvent* aMouseEvent); + NS_IMETHOD MouseDblClick(nsIDOMEvent* aMouseEvent); + NS_IMETHOD MouseOver(nsIDOMEvent* aMouseEvent); + NS_IMETHOD MouseOut(nsIDOMEvent* aMouseEvent); + /*END implementations of mouseevent handler interface*/ + + /* BEGIN interfaces in to the keylistener interface. */ + NS_IMETHOD KeyDown(nsIDOMEvent* aKeyEvent); + NS_IMETHOD KeyUp(nsIDOMEvent* aKeyEvent); + NS_IMETHOD KeyPress(nsIDOMEvent* aKeyEvent); + /* END interfaces from nsIDOMKeyListener */ + + mozInlineSpellChecker(); + virtual ~mozInlineSpellChecker(); + + // spell check the selected text specified by aSelection + nsresult SpellCheckSelection(nsISelection *aSelection, + nsISelection *aSpellCheckSelection); + + // spell checks all of the words between two nodes + nsresult SpellCheckBetweenNodes(nsIDOMNode *aStartNode, + PRInt32 aStartOffset, + nsIDOMNode *aEndNode, + PRInt32 aEndOffset, + nsISelection *aSpellCheckSelection); + // examines the dom node in question and returns true if the inline spell + // checker should skip the node (i.e. the text is inside of a block quote + // or an e-mail signature...) + nsresult CheckShouldSpellCheck(nsIDOMNode *aNode, PRBool * aCheckSpelling); + + nsresult AdjustSpellHighlighting(nsIDOMNode *aNode, + PRInt32 aOffset, + nsISelection *aSpellCheckSelection, + PRBool isDeletion); + + // spell check the text contained within aRange + nsresult SpellCheckRange(nsIDOMRange *aRange, nsISelection *aSpellCheckSelection); + + // helper routine to determine if a point is inside of a the passed in selection. + nsresult IsPointInSelection(nsISelection *aSelection, + nsIDOMNode *aNode, + PRInt32 aOffset, + nsIDOMRange **aRange); + + nsresult CleanupRangesInSelection(nsISelection *aSelection); + nsresult RemoveCurrentWordFromSpellSelection(nsISelection *aSpellCheckSelection, nsIDOMRange * aWordRange); + + // DOM and editor event registration helper routines + nsresult RegisterEventListeners(); + nsresult UnregisterEventListeners(); + nsresult HandleNavigationEvent(nsIDOMEvent * aEvent, PRBool aForceWordSpellCheck, PRInt32 aNewPositionOffset = 0); + + + // helper routine which expands a point in a text node out to the range for the containing word. + nsresult GenerateRangeForSurroundingWord(nsIDOMNode * aNode, PRInt32 aOffset, nsIDOMRange ** aWordRange); + + // helper routine for determining if aNode/aOffset is an end of word delimiter. + PRBool EndOfAWord(nsIDOMNode *aNode, PRInt32 aOffset); + + nsresult GetSpellCheckSelection(nsISelection ** aSpellCheckSelection); + nsresult SaveCurrentSelectionPosition(); +}; + +#endif /* __mozinlinespellchecker_h__ */ diff --git a/mozilla/extensions/spellcheck/src/mozSpellCheckerFactory.cpp b/mozilla/extensions/spellcheck/src/mozSpellCheckerFactory.cpp index 79ea16c5041..9b9eab6e29e 100644 --- a/mozilla/extensions/spellcheck/src/mozSpellCheckerFactory.cpp +++ b/mozilla/extensions/spellcheck/src/mozSpellCheckerFactory.cpp @@ -39,6 +39,7 @@ #include "nsIGenericFactory.h" #include "mozSpellChecker.h" +#include "mozInlineSpellChecker.h" #include "nsTextServicesCID.h" #include "mozPersonalDictionary.h" #include "mozSpellI18NManager.h" @@ -48,6 +49,11 @@ 0x8227F019, 0xAFC7, 0x461e, \ { 0xB0, 0x30, 0x9F, 0x18, 0x5D, 0x7A, 0x0E, 0x29} } +#define MOZ_INLINESPELLCHECKER_CID \ +{ /* 9FE5D975-09BD-44aa-A01A-66402EA28657 */ \ +0x9fe5d975, 0x9bd, 0x44aa, \ +{ 0xa0, 0x1a, 0x66, 0x40, 0x2e, 0xa2, 0x86, 0x57} } + //////////////////////////////////////////////////////////////////////// // Define the contructor function for the objects // @@ -57,6 +63,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(mozSpellChecker) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(mozPersonalDictionary, Init) NS_GENERIC_FACTORY_CONSTRUCTOR(mozSpellI18NManager) +NS_GENERIC_FACTORY_CONSTRUCTOR(mozInlineSpellChecker) //////////////////////////////////////////////////////////////////////// // Define a table of CIDs implemented by this module along with other @@ -66,7 +73,8 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(mozSpellI18NManager) static nsModuleComponentInfo components[] = { { NULL, NS_SPELLCHECKER_CID, NS_SPELLCHECKER_CONTRACTID, mozSpellCheckerConstructor }, { NULL, MOZ_PERSONALDICTIONARY_CID, MOZ_PERSONALDICTIONARY_CONTRACTID, mozPersonalDictionaryConstructor }, - { NULL, MOZ_SPELLI18NMANAGER_CID, MOZ_SPELLI18NMANAGER_CONTRACTID, mozSpellI18NManagerConstructor } + { NULL, MOZ_SPELLI18NMANAGER_CID, MOZ_SPELLI18NMANAGER_CONTRACTID, mozSpellI18NManagerConstructor }, + { NULL, MOZ_INLINESPELLCHECKER_CID, MOZ_INLINESPELLCHECKER_CONTRACTID, mozInlineSpellCheckerConstructor } }; ////////////////////////////////////////////////////////////////////////