diff --git a/mozilla/editor/composer/src/nsComposeTxtSrvFilter.cpp b/mozilla/editor/composer/src/nsComposeTxtSrvFilter.cpp index 1b487ae8157..e013d93fee4 100644 --- a/mozilla/editor/composer/src/nsComposeTxtSrvFilter.cpp +++ b/mozilla/editor/composer/src/nsComposeTxtSrvFilter.cpp @@ -49,6 +49,7 @@ nsComposeTxtSrvFilter::nsComposeTxtSrvFilter() : mPreAtom = do_GetAtom("pre"); mSpanAtom = do_GetAtom("span"); mMozQuoteAtom = do_GetAtom("_moz_quote"); + mClassAtom = do_GetAtom("class"); mTypeAtom = do_GetAtom("type"); mScriptAtom = do_GetAtom("script"); mTextAreaAtom = do_GetAtom("textarea"); @@ -82,6 +83,11 @@ nsComposeTxtSrvFilter::Skip(nsIDOMNode* aNode, PRBool *_retval) if (NS_SUCCEEDED(content->GetAttr(kNameSpaceID_None, mMozQuoteAtom, mozQuote))) { *_retval = mozQuote.LowerCaseEqualsLiteral("true"); } + + nsAutoString className; + if (NS_SUCCEEDED(content->GetAttr(kNameSpaceID_None, mClassAtom, className))) { + *_retval = className.EqualsLiteral("moz-signature"); + } } } else if (tag == mScriptAtom || tag == mTextAreaAtom || diff --git a/mozilla/editor/composer/src/nsComposeTxtSrvFilter.h b/mozilla/editor/composer/src/nsComposeTxtSrvFilter.h index 571fa9f0b51..5d1d3183cf9 100644 --- a/mozilla/editor/composer/src/nsComposeTxtSrvFilter.h +++ b/mozilla/editor/composer/src/nsComposeTxtSrvFilter.h @@ -69,6 +69,7 @@ protected: nsCOMPtr mPreAtom; // mail plain text quotes are wrapped in pre tags nsCOMPtr mSpanAtom; //or they may be wrapped in span tags (editor.quotesPreformatted). nsCOMPtr mMozQuoteAtom; // _moz_quote_ + nsCOMPtr mClassAtom; nsCOMPtr mTypeAtom; nsCOMPtr mScriptAtom; nsCOMPtr mTextAreaAtom; diff --git a/mozilla/editor/composer/src/nsEditorSpellCheck.cpp b/mozilla/editor/composer/src/nsEditorSpellCheck.cpp index d5b992dfd66..c140f9efe33 100644 --- a/mozilla/editor/composer/src/nsEditorSpellCheck.cpp +++ b/mozilla/editor/composer/src/nsEditorSpellCheck.cpp @@ -23,6 +23,7 @@ * Kin Blas * Akkana Peck * Charley Manske + * Neil Deakin * * 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 @@ -239,6 +240,17 @@ nsEditorSpellCheck::CheckCurrentWord(const PRUnichar *aSuggestedWord, aIsMisspelled, &mSuggestedWordList); } +NS_IMETHODIMP +nsEditorSpellCheck::CheckCurrentWordNoSuggest(const PRUnichar *aSuggestedWord, + PRBool *aIsMisspelled) +{ + if (!mSpellChecker) + return NS_NOINTERFACE; + + return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord), + aIsMisspelled, nsnull); +} + NS_IMETHODIMP nsEditorSpellCheck::ReplaceWord(const PRUnichar *aMisspelledWord, const PRUnichar *aReplaceWord, diff --git a/mozilla/editor/idl/nsIEditor.idl b/mozilla/editor/idl/nsIEditor.idl index 5de11cb4cca..d052554e2c2 100644 --- a/mozilla/editor/idl/nsIEditor.idl +++ b/mozilla/editor/idl/nsIEditor.idl @@ -51,6 +51,7 @@ interface nsITransactionManager; interface nsITransaction; interface nsIEditorObserver; interface nsIEditActionListener; +interface nsIInlineSpellChecker; %{C++ class nsIPresShell; @@ -62,8 +63,7 @@ typedef short EDirection; [ptr] native nsIPresShellPtr(nsIPresShell); [ptr] native nsIContentPtr(nsIContent); - -[scriptable, uuid(e456440e-2e2b-4097-b3ec-60dc22b0aa9a)] +[scriptable, uuid(D4882FFB-E927-408b-96BE-D4391B456FA9)] interface nsIEditor : nsISupports { @@ -283,6 +283,10 @@ interface nsIEditor : nsISupports */ void setShouldTxnSetSelection(in boolean should); + /* ------------ Inline Spell Checking methods -------------- */ + + readonly attribute nsIInlineSpellChecker inlineSpellChecker; + /* ------------ Clipboard methods -------------- */ /** cut the currently selected text, putting it into the OS clipboard diff --git a/mozilla/editor/idl/nsIEditorSpellCheck.idl b/mozilla/editor/idl/nsIEditorSpellCheck.idl index a5ae7f564b3..9e8a98fbbe2 100644 --- a/mozilla/editor/idl/nsIEditorSpellCheck.idl +++ b/mozilla/editor/idl/nsIEditorSpellCheck.idl @@ -40,7 +40,7 @@ interface nsIEditor; interface nsITextServicesFilter; -[scriptable, uuid(87ce8b81-1cf2-11d3-9ce4-c60a16061e7c)] +[scriptable, uuid(6088a862-1229-11d9-941d-c035b2e390c6)] interface nsIEditorSpellCheck : nsISupports { @@ -59,5 +59,6 @@ interface nsIEditorSpellCheck : nsISupports void SetCurrentDictionary(in wstring dictionary); void UninitSpellChecker(); void setFilter(in nsITextServicesFilter filter); + boolean CheckCurrentWordNoSuggest(in wstring suggestedWord); }; diff --git a/mozilla/editor/libeditor/base/Makefile.in b/mozilla/editor/libeditor/base/Makefile.in index 7543da99997..79bfb69b0d1 100644 --- a/mozilla/editor/libeditor/base/Makefile.in +++ b/mozilla/editor/libeditor/base/Makefile.in @@ -53,12 +53,14 @@ REQUIRES = xpcom \ layout \ content \ txmgr \ + txtsvc \ htmlparser \ necko \ pref \ view \ gfx \ widget \ + lwbrk \ unicharutil \ commandhandler \ docshell \ diff --git a/mozilla/editor/libeditor/base/nsEditor.cpp b/mozilla/editor/libeditor/base/nsEditor.cpp index c71cb386973..fe50caa3dac 100644 --- a/mozilla/editor/libeditor/base/nsEditor.cpp +++ b/mozilla/editor/libeditor/base/nsEditor.cpp @@ -101,6 +101,7 @@ #include "nsEditor.h" #include "nsEditorUtils.h" #include "nsISelectionDisplay.h" +#include "nsIInlineSpellChecker.h" #include "nsINameSpaceManager.h" #include "nsIHTMLDocument.h" @@ -215,6 +216,9 @@ nsEditor::~nsEditor() delete mEditorObservers; // no need to release observers; we didn't addref them mEditorObservers = 0; + if (mInlineSpellChecker) + mInlineSpellChecker->Cleanup(); + if (mActionListeners) { PRInt32 i; @@ -1141,6 +1145,22 @@ nsEditor::MarkNodeDirty(nsIDOMNode* aNode) return NS_OK; } +NS_IMETHODIMP nsEditor::GetInlineSpellChecker(nsIInlineSpellChecker ** aInlineSpellChecker) +{ + NS_ENSURE_ARG_POINTER(aInlineSpellChecker); + nsresult rv; + + if (!mInlineSpellChecker) { + mInlineSpellChecker = do_CreateInstance(MOZ_INLINESPELLCHECKER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mInlineSpellChecker->Init(this); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_IF_ADDREF(*aInlineSpellChecker = mInlineSpellChecker); + return NS_OK; +} #ifdef XP_MAC #pragma mark - @@ -5234,6 +5254,26 @@ nsEditor::RemoveAttributeOrEquivalent(nsIDOMElement * aElement, return RemoveAttribute(aElement, aAttribute); } +nsresult +nsEditor::HandleInlineSpellCheck(PRInt32 action, + nsISelection *aSelection, + nsIDOMNode *previousSelectedNode, + PRInt32 previousSelectedOffset, + nsIDOMNode *aStartNode, + PRInt32 aStartOffset, + nsIDOMNode *aEndNode, + PRInt32 aEndOffset) +{ + return mInlineSpellChecker ? mInlineSpellChecker->SpellCheckAfterEditorChange(action, + aSelection, + previousSelectedNode, + previousSelectedOffset, + aStartNode, + aStartOffset, + aEndNode, + aEndOffset) : NS_OK; +} + NS_IMETHODIMP nsEditor::SwitchTextDirection() { diff --git a/mozilla/editor/libeditor/base/nsEditor.h b/mozilla/editor/libeditor/base/nsEditor.h index e432d316ac5..90ac39610d6 100644 --- a/mozilla/editor/libeditor/base/nsEditor.h +++ b/mozilla/editor/libeditor/base/nsEditor.h @@ -59,6 +59,8 @@ #include "nsIDTD.h" #include "nsIDOMElement.h" #include "nsSelectionState.h" +#include "nsIEditorSpellCheck.h" +#include "nsIInlineSpellChecker.h" class nsIEditActionListener; class nsIDocumentStateListener; @@ -547,6 +549,15 @@ public: PRBool GetShouldTxnSetSelection(); + nsresult HandleInlineSpellCheck(PRInt32 action, + nsISelection *aSelection, + nsIDOMNode *previousSelectedNode, + PRInt32 previousSelectedOffset, + nsIDOMNode *aStartNode, + PRInt32 aStartOffset, + nsIDOMNode *aEndNode, + PRInt32 aEndOffset); + public: // Argh! These transaction names are used by PlaceholderTxn and // nsPlaintextEditor. They should be localized to those classes. @@ -563,6 +574,7 @@ protected: nsWeakPtr mSelConWeak; // weak reference to the nsISelectionController nsIViewManager *mViewManager; PRInt32 mUpdateCount; + nsCOMPtr mInlineSpellChecker; // used for real-time spellchecking nsCOMPtr mTxnMgr; nsWeakPtr mPlaceHolderTxn; // weak reference to placeholder for begin/end batch purposes nsIAtom *mPlaceHolderName; // name of placeholder transaction diff --git a/mozilla/editor/libeditor/html/Makefile.in b/mozilla/editor/libeditor/html/Makefile.in index 2813cbe94a4..493598910b1 100644 --- a/mozilla/editor/libeditor/html/Makefile.in +++ b/mozilla/editor/libeditor/html/Makefile.in @@ -54,6 +54,7 @@ REQUIRES = xpcom \ unicharutil \ content \ txmgr \ + txtsvc \ htmlparser \ necko \ pref \ diff --git a/mozilla/editor/libeditor/html/nsHTMLDataTransfer.cpp b/mozilla/editor/libeditor/html/nsHTMLDataTransfer.cpp index d4024d8148c..138cbc5399a 100644 --- a/mozilla/editor/libeditor/html/nsHTMLDataTransfer.cpp +++ b/mozilla/editor/libeditor/html/nsHTMLDataTransfer.cpp @@ -280,7 +280,22 @@ nsHTMLEditor::InsertHTMLWithContext(const nsAString & aInputString, // create a dom document fragment that represents the structure to paste nsCOMPtr fragmentAsNode; PRInt32 rangeStartHint, rangeEndHint; - res = CreateDOMFragmentFromPaste(aInputString, aContextStr, aInfoStr, + + nsAutoString contextStr; + contextStr.Assign(aContextStr); + +#ifdef MOZ_THUNDERBIRD + // See Bug #228920 --> editor / parser has trouble inserting single cell data from Excel. + // The details are in the bug. Until we figure out why the parser is not building the right + // document structure for the single cell paste case, we can explicitly check for just such + // a condition and work around it. By setting the contextStr to an empty string we end up + // pasting just the cell text which is what we want anyway. + // A paste from an excel cell always starts with a new line, two spaces and then the td tag + if (StringBeginsWith(aInputString, NS_LITERAL_STRING("\n * Daniel Glazman + * Neil Deakin * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), @@ -425,13 +426,16 @@ nsHTMLEditRules::AfterEditInner(PRInt32 action, nsIEditor::EDirection aDirection nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; + nsCOMPtr rangeStartParent, rangeEndParent; + PRInt32 rangeStartOffset = 0, rangeEndOffset = 0; // do we have a real range to act on? PRBool bDamagedRange = PR_FALSE; if (mDocChangeRange) { - nsCOMPtr rangeStartParent, rangeEndParent; mDocChangeRange->GetStartContainer(getter_AddRefs(rangeStartParent)); mDocChangeRange->GetEndContainer(getter_AddRefs(rangeEndParent)); + mDocChangeRange->GetStartOffset(&rangeStartOffset); + mDocChangeRange->GetEndOffset(&rangeEndOffset); if (rangeStartParent && rangeEndParent) bDamagedRange = PR_TRUE; } @@ -540,11 +544,19 @@ nsHTMLEditRules::AfterEditInner(PRInt32 action, nsIEditor::EDirection aDirection } } + res = mHTMLEditor->HandleInlineSpellCheck(action, selection, + mRangeItem.startNode, mRangeItem.startOffset, + rangeStartParent, rangeStartOffset, + rangeEndParent, rangeEndOffset); + if (NS_FAILED(res)) + return res; + // detect empty doc res = CreateBogusNodeIfNeeded(selection); // adjust selection HINT if needed - if (NS_FAILED(res)) return res; + if (NS_FAILED(res)) + return res; if (!mDidExplicitlySetInterline) { diff --git a/mozilla/editor/libeditor/html/nsHTMLEditor.h b/mozilla/editor/libeditor/html/nsHTMLEditor.h index 074f8827ca6..f31e70292fa 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditor.h +++ b/mozilla/editor/libeditor/html/nsHTMLEditor.h @@ -48,6 +48,7 @@ #include "nsITableEditor.h" #include "nsIEditorMailSupport.h" #include "nsIEditorStyleSheets.h" +#include "nsITextServicesDocument.h" #include "nsEditor.h" #include "nsIDOMElement.h" @@ -788,6 +789,9 @@ protected: // an array for holding default style settings nsVoidArray mDefaultStyles; + // for real-time spelling + nsCOMPtr mTextServices; + // Maintain a static parser service ... static nsIParserService* sParserService; diff --git a/mozilla/editor/libeditor/text/Makefile.in b/mozilla/editor/libeditor/text/Makefile.in index dd1dda2015c..38b51c2dddb 100644 --- a/mozilla/editor/libeditor/text/Makefile.in +++ b/mozilla/editor/libeditor/text/Makefile.in @@ -53,6 +53,7 @@ REQUIRES = xpcom \ layout \ content \ txmgr \ + txtsvc \ htmlparser \ necko \ pref \ diff --git a/mozilla/editor/libeditor/text/nsTextEditRules.cpp b/mozilla/editor/libeditor/text/nsTextEditRules.cpp index 1807c8b29ed..3ca5dd261c6 100644 --- a/mozilla/editor/libeditor/text/nsTextEditRules.cpp +++ b/mozilla/editor/libeditor/text/nsTextEditRules.cpp @@ -49,7 +49,9 @@ #include "nsIDOMNodeList.h" #include "nsISelection.h" #include "nsISelectionPrivate.h" +#include "nsISelectionController.h" #include "nsIDOMRange.h" +#include "nsIDOMNSRange.h" #include "nsIDOMCharacterData.h" #include "nsIContent.h" #include "nsIContentIterator.h" @@ -58,6 +60,8 @@ #include "nsIPrefBranch.h" #include "nsIPrefService.h" #include "nsUnicharUtils.h" +#include "nsIWordBreakerFactory.h" +#include "nsLWBrkCIID.h" // for IBMBIDI #include "nsIPresShell.h" @@ -196,6 +200,15 @@ nsTextEditRules::BeforeEdit(PRInt32 action, nsIEditor::EDirection aDirection) nsAutoLockRulesSniffing lockIt(this); mDidExplicitlySetInterline = PR_FALSE; + // get the selection and cache the position before editing + nsCOMPtr selection; + nsresult res = mEditor->GetSelection(getter_AddRefs(selection)); + if (NS_FAILED(res)) + return res; + + selection->GetAnchorNode(getter_AddRefs(mCachedSelectionNode)); + selection->GetAnchorOffset(&mCachedSelectionOffset); + if (!mActionNesting) { // let rules remember the top level action @@ -221,13 +234,21 @@ nsTextEditRules::AfterEdit(PRInt32 action, nsIEditor::EDirection aDirection) res = mEditor->GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; + res = mEditor->HandleInlineSpellCheck(action, selection, + mCachedSelectionNode, mCachedSelectionOffset, + nsnull, 0, nsnull, 0); + if (NS_FAILED(res)) + return res; + // detect empty doc res = CreateBogusNodeIfNeeded(selection); - if (NS_FAILED(res)) return res; + if (NS_FAILED(res)) + return res; // insure trailing br node res = CreateTrailingBRIfNeeded(); - if (NS_FAILED(res)) return res; + if (NS_FAILED(res)) + return res; /* After inserting text the cursor Bidi level must be set to the level of the inserted text. * This is difficult, because we cannot know what the level is until after the Bidi algorithm diff --git a/mozilla/editor/libeditor/text/nsTextEditRules.h b/mozilla/editor/libeditor/text/nsTextEditRules.h index 874374a3133..d5a78a6ecd1 100644 --- a/mozilla/editor/libeditor/text/nsTextEditRules.h +++ b/mozilla/editor/libeditor/text/nsTextEditRules.h @@ -203,6 +203,8 @@ protected: PRInt32 mPasswordIMEIndex; nsCOMPtr mBogusNode; // magic node acts as placeholder in empty doc nsCOMPtr mBody; // cached root node + nsCOMPtr mCachedSelectionNode; // cached selected node + PRInt32 mCachedSelectionOffset; // cached selected offset PRUint32 mFlags; PRUint32 mActionNesting; PRPackedBool mLockRulesSniffing; diff --git a/mozilla/editor/txtsvc/public/Makefile.in b/mozilla/editor/txtsvc/public/Makefile.in index 0fcedde7708..2339ab77eb4 100644 --- a/mozilla/editor/txtsvc/public/Makefile.in +++ b/mozilla/editor/txtsvc/public/Makefile.in @@ -54,6 +54,7 @@ EXPORTS = \ XPIDLSRCS = \ nsITextServicesFilter.idl \ + nsIInlineSpellChecker.idl \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/mozilla/editor/txtsvc/public/nsIInlineSpellChecker.idl b/mozilla/editor/txtsvc/public/nsIInlineSpellChecker.idl new file mode 100644 index 00000000000..a03bcbdbb4c --- /dev/null +++ b/mozilla/editor/txtsvc/public/nsIInlineSpellChecker.idl @@ -0,0 +1,78 @@ +/* -*- 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 Spellchecking + * + * The Initial Developer of the Original Code is Neil Deakin. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Neil Deakin (enndeakin@sympatico.ca) + * 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 "nsISupports.idl" +#include "domstubs.idl" + +interface nsISelection; +interface nsIEditor; +interface nsIEditorSpellCheck; + +[scriptable, uuid(f5d1ec9e-4d30-11d8-8053-da0cc7df1f20)] + +interface nsIInlineSpellChecker : nsISupports +{ + readonly attribute nsIEditorSpellCheck spellChecker; + + [noscript] void init(in nsIEditor aEditor); + [noscript] void cleanup(); + + attribute boolean enableRealTimeSpell; + + void spellCheckAfterEditorChange(in PRInt32 aAction, + in nsISelection aSelection, + in nsIDOMNode aPreviousSelectedNode, + in PRInt32 aPreviousSelectedOffset, + in nsIDOMNode aStartNode, + in PRInt32 aStartOffset, + in nsIDOMNode aEndNode, + in PRInt32 aEndOffset); + + void spellCheckRange(in nsIDOMRange aSelection); + + nsIDOMRange getMispelledWord(in nsIDOMNode aNode, in PRInt32 aOffset); + void replaceWord(in nsIDOMNode aNode, in PRInt32 aOffset, in AString aNewword); + void addWordToDictionary(in AString aWord); + void ignoreWord(in AString aWord); +}; + +%{C++ + +#define MOZ_INLINESPELLCHECKER_CONTRACTID "@mozilla.org/spellchecker-inline;1" + +%} diff --git a/mozilla/editor/txtsvc/public/nsITextServicesDocument.h b/mozilla/editor/txtsvc/public/nsITextServicesDocument.h index 8906c968340..14beb7bd6b8 100644 --- a/mozilla/editor/txtsvc/public/nsITextServicesDocument.h +++ b/mozilla/editor/txtsvc/public/nsITextServicesDocument.h @@ -266,6 +266,14 @@ public: */ NS_IMETHOD SetDisplayStyle(TSDDisplayStyle aStyle) = 0; + + /** + * Returns the DOM range for a given offset and length + * @param aOffset offset into string returned by GetCurrentTextBlock(). + * @param aLength number characters selected. + * @return aDOMRange the DOM range that represents the offset and length + */ + NS_IMETHOD GetDOMRangeFor(PRInt32 aOffset, PRInt32 aLength, nsIDOMRange** aRange) = 0; }; #endif // nsITextServicesDocument_h__ diff --git a/mozilla/editor/txtsvc/src/nsTextServicesDocument.cpp b/mozilla/editor/txtsvc/src/nsTextServicesDocument.cpp index efb8e243e35..92e7a83a41d 100644 --- a/mozilla/editor/txtsvc/src/nsTextServicesDocument.cpp +++ b/mozilla/editor/txtsvc/src/nsTextServicesDocument.cpp @@ -21,6 +21,7 @@ * * Contributor(s): * Pierre Phaneuf + * Neil Deakin * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), @@ -115,8 +116,9 @@ nsTextServicesDocument::nsTextServicesDocument() nsTextServicesDocument::~nsTextServicesDocument() { - if (mEditor && mNotifier) - mEditor->RemoveEditActionListener(mNotifier); + nsCOMPtr editor (do_QueryReferent(mEditor)); + if (editor && mNotifier) + editor->RemoveEditActionListener(mNotifier); ClearOffsetTable(&mOffsetTable); } @@ -281,8 +283,7 @@ nsTextServicesDocument::InitWithEditor(nsIEditor *aEditor) } } - mEditor = do_QueryInterface(aEditor); - + mEditor = do_GetWeakReference(aEditor); nsTSDNotifier *notifier = new nsTSDNotifier(this); if (!notifier) @@ -293,7 +294,7 @@ nsTextServicesDocument::InitWithEditor(nsIEditor *aEditor) mNotifier = do_QueryInterface(notifier); - result = mEditor->AddEditActionListener(mNotifier); + result = aEditor->AddEditActionListener(mNotifier); UNLOCK_DOC(this); @@ -321,8 +322,6 @@ nsTextServicesDocument::SetExtent(nsIDOMRange* aDOMRange) { NS_ENSURE_ARG_POINTER(aDOMRange); NS_ENSURE_TRUE(mDOMDocument, NS_ERROR_FAILURE); - NS_ENSURE_TRUE(mEditor, NS_ERROR_FAILURE); - NS_ENSURE_TRUE(mNotifier, NS_ERROR_FAILURE); LOCK_DOC(this); @@ -548,10 +547,10 @@ nsTextServicesDocument::ExpandRangeToWordBoundaries(nsIDOMRange *aRange) // Now adjust the range so that it uses our new // end points. - result = aRange->SetStart(rngStartNode, rngStartOffset); + result = aRange->SetEnd(rngEndNode, rngEndOffset); NS_ENSURE_SUCCESS(result, result); - return aRange->SetEnd(rngEndNode, rngEndOffset); + return aRange->SetStart(rngStartNode, rngStartOffset); } NS_IMETHODIMP @@ -569,7 +568,9 @@ nsTextServicesDocument::CanEdit(PRBool *aCanEdit) if (!aCanEdit) return NS_ERROR_NULL_POINTER; - *aCanEdit = (mEditor) ? PR_TRUE : PR_FALSE; + nsCOMPtr editor (do_QueryReferent(mEditor)); + + *aCanEdit = (editor) ? PR_TRUE : PR_FALSE; return NS_OK; } @@ -1850,11 +1851,11 @@ nsTextServicesDocument::DeleteSelection() nsresult result = NS_OK; // We don't allow deletion during a collapsed selection! - - NS_ASSERTION(mEditor, "DeleteSelection called without an editor present!"); + nsCOMPtr editor (do_QueryReferent(mEditor)); + NS_ASSERTION(editor, "DeleteSelection called without an editor present!"); NS_ASSERTION(SelectionIsValid(), "DeleteSelection called without a valid selection!"); - if (!mEditor || !SelectionIsValid()) + if (!editor || !SelectionIsValid()) return NS_ERROR_FAILURE; if (SelectionIsCollapsed()) @@ -2017,7 +2018,7 @@ nsTextServicesDocument::DeleteSelection() // Now delete the actual content! - result = mEditor->DeleteSelection(nsIEditor::ePrevious); + result = editor->DeleteSelection(nsIEditor::ePrevious); if (NS_FAILED(result)) { @@ -2150,9 +2151,10 @@ nsTextServicesDocument::InsertText(const nsString *aText) { nsresult result = NS_OK; - NS_ASSERTION(mEditor, "InsertText called without an editor present!"); + nsCOMPtr editor (do_QueryReferent(mEditor)); + NS_ASSERTION(editor, "InsertText called without an editor present!"); - if (!mEditor || !SelectionIsValid()) + if (!editor || !SelectionIsValid()) return NS_ERROR_FAILURE; if (!aText) @@ -2183,7 +2185,7 @@ nsTextServicesDocument::InsertText(const nsString *aText) LOCK_DOC(this); - result = mEditor->BeginTransaction(); + result = editor->BeginTransaction(); if (NS_FAILED(result)) { @@ -2191,13 +2193,13 @@ nsTextServicesDocument::InsertText(const nsString *aText) return result; } - nsCOMPtr textEditor (do_QueryInterface(mEditor, &result)); + nsCOMPtr textEditor (do_QueryInterface(editor, &result)); if (textEditor) result = textEditor->InsertText(*aText); if (NS_FAILED(result)) { - mEditor->EndTransaction(); + editor->EndTransaction(); UNLOCK_DOC(this); return result; } @@ -2235,7 +2237,7 @@ nsTextServicesDocument::InsertText(const nsString *aText) if (!itEntry) { - mEditor->EndTransaction(); + editor->EndTransaction(); UNLOCK_DOC(this); return NS_ERROR_OUT_OF_MEMORY; } @@ -2245,7 +2247,7 @@ nsTextServicesDocument::InsertText(const nsString *aText) if (!mOffsetTable.InsertElementAt(itEntry, mSelStartIndex)) { - mEditor->EndTransaction(); + editor->EndTransaction(); UNLOCK_DOC(this); return NS_ERROR_FAILURE; } @@ -2267,7 +2269,7 @@ nsTextServicesDocument::InsertText(const nsString *aText) if (!itEntry) { - mEditor->EndTransaction(); + editor->EndTransaction(); UNLOCK_DOC(this); return NS_ERROR_FAILURE; } @@ -2288,7 +2290,7 @@ nsTextServicesDocument::InsertText(const nsString *aText) if (!itEntry) { - mEditor->EndTransaction(); + editor->EndTransaction(); UNLOCK_DOC(this); return NS_ERROR_OUT_OF_MEMORY; } @@ -2315,7 +2317,7 @@ nsTextServicesDocument::InsertText(const nsString *aText) if (NS_FAILED(result)) { - mEditor->EndTransaction(); + editor->EndTransaction(); UNLOCK_DOC(this); return result; } @@ -2324,7 +2326,7 @@ nsTextServicesDocument::InsertText(const nsString *aText) if (NS_FAILED(result)) { - mEditor->EndTransaction(); + editor->EndTransaction(); UNLOCK_DOC(this); return result; } @@ -2341,7 +2343,7 @@ nsTextServicesDocument::InsertText(const nsString *aText) if (NS_FAILED(result)) { - mEditor->EndTransaction(); + editor->EndTransaction(); UNLOCK_DOC(this); return result; } @@ -2350,7 +2352,7 @@ nsTextServicesDocument::InsertText(const nsString *aText) if (!itEntry) { - mEditor->EndTransaction(); + editor->EndTransaction(); UNLOCK_DOC(this); return NS_ERROR_OUT_OF_MEMORY; } @@ -2360,7 +2362,7 @@ nsTextServicesDocument::InsertText(const nsString *aText) if (!mOffsetTable.InsertElementAt(itEntry, mSelStartIndex + 1)) { - mEditor->EndTransaction(); + editor->EndTransaction(); UNLOCK_DOC(this); return NS_ERROR_FAILURE; } @@ -2397,7 +2399,7 @@ nsTextServicesDocument::InsertText(const nsString *aText) if (NS_FAILED(result)) { - mEditor->EndTransaction(); + editor->EndTransaction(); UNLOCK_DOC(this); return result; } @@ -2406,13 +2408,13 @@ nsTextServicesDocument::InsertText(const nsString *aText) if (NS_FAILED(result)) { - mEditor->EndTransaction(); + editor->EndTransaction(); UNLOCK_DOC(this); return result; } } - result = mEditor->EndTransaction(); + result = editor->EndTransaction(); UNLOCK_DOC(this); @@ -2425,18 +2427,74 @@ nsTextServicesDocument::SetDisplayStyle(TSDDisplayStyle aStyle) return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +nsTextServicesDocument::GetDOMRangeFor(PRInt32 aOffset, PRInt32 aLength, nsIDOMRange** aRange) +{ + if (!mSelCon || aOffset < 0 || aLength < 0) + return NS_ERROR_FAILURE; + + nsIDOMNode *sNode = 0, *eNode = 0; + PRInt32 i, sOffset = 0, eOffset = 0; + OffsetEntry *entry; + + // Find the start + for (i = 0; !sNode && i < mOffsetTable.Count(); i++) + { + entry = (OffsetEntry *)mOffsetTable[i]; + if (entry->mIsValid) + { + if (entry->mIsInsertedText) + { + if (entry->mStrOffset == aOffset) + { + sNode = entry->mNode; + sOffset = entry->mNodeOffset + entry->mLength; + } + } + else if (aOffset >= entry->mStrOffset && aOffset <= entry->mStrOffset + entry->mLength) + { + sNode = entry->mNode; + sOffset = entry->mNodeOffset + aOffset - entry->mStrOffset; + } + } + } + + if (!sNode) + return NS_ERROR_FAILURE; + + // Now find the end + PRInt32 endOffset = aOffset + aLength; + + for (i = mOffsetTable.Count() - 1; !eNode && i >= 0; i--) + { + entry = (OffsetEntry *)mOffsetTable[i]; + + if (entry->mIsValid) + { + if (entry->mIsInsertedText) + { + if (entry->mStrOffset == eOffset) + { + eNode = entry->mNode; + eOffset = entry->mNodeOffset + entry->mLength; + } + } + else if (endOffset >= entry->mStrOffset && endOffset <= entry->mStrOffset + entry->mLength) + { + eNode = entry->mNode; + eOffset = entry->mNodeOffset + endOffset - entry->mStrOffset; + } + } + } + + return CreateRange(sNode, sOffset, eNode, eOffset, aRange); +} + nsresult nsTextServicesDocument::InsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aPosition) { - //**** KDEBUG **** - // printf("** InsertNode: 0x%.8x 0x%.8x %d\n", aNode, aParent, aPosition); - // fflush(stdout); - //**** KDEBUG **** - - NS_ASSERTION(0, "InsertNode called, offset tables might be out of sync."); - return NS_OK; } @@ -2499,7 +2557,6 @@ nsTextServicesDocument::DeleteNode(nsIDOMNode *aChild) if (entry->mNode == aChild) { - NS_ASSERTION(!entry->mIsValid, "DeleteNode called for a valid node! Offset table is out of sync."); entry->mIsValid = PR_FALSE; } @@ -2520,9 +2577,6 @@ nsTextServicesDocument::SplitNode(nsIDOMNode *aExistingRightNode, // printf("** SplitNode: 0x%.8x %d 0x%.8x\n", aExistingRightNode, aOffset, aNewLeftNode); // fflush(stdout); //**** KDEBUG **** - - NS_ASSERTION(0, "SplitNode called, offset tables might be out of sync."); - return NS_OK; } @@ -2589,8 +2643,6 @@ nsTextServicesDocument::JoinNodes(nsIDOMNode *aLeftNode, if (!rightHasEntry) { - // XXX: Not sure if we should be throwing an error here! - NS_ASSERTION(0, "JoinNode called with node not listed in offset table."); return NS_ERROR_FAILURE; } @@ -4579,6 +4631,13 @@ nsTextServicesDocument::GetWordBreaker(nsIWordBreaker** aResult) return result; } +// Spellchecker code has this. See bug 211343 +#ifdef XP_MAC +#define IS_NBSP_CHAR(c) (((unsigned char)0xca)==(c)) +#else +#define IS_NBSP_CHAR(c) (((unsigned char)0xa0)==(c)) +#endif + nsresult nsTextServicesDocument::FindWordBounds(nsVoidArray *aOffsetTable, nsString *aBlockStr, @@ -4628,6 +4687,18 @@ nsTextServicesDocument::FindWordBounds(nsVoidArray *aOffsetTable, &beginWord, &endWord); NS_ENSURE_SUCCESS(result, result); + // Strip out the NBSPs at the ends + while ((beginWord <= endWord) && (IS_NBSP_CHAR(str[beginWord]))) + beginWord++; + if (str[endWord] == (unsigned char)0x20) + { + PRUint32 realEndWord = endWord - 1; + while ((realEndWord > beginWord) && (IS_NBSP_CHAR(str[realEndWord]))) + realEndWord--; + if (realEndWord < endWord - 1) + endWord = realEndWord + 1; + } + // Now that we have the string offsets for the beginning // and end of the word, run through the offset table and // convert them back into dom points. diff --git a/mozilla/editor/txtsvc/src/nsTextServicesDocument.h b/mozilla/editor/txtsvc/src/nsTextServicesDocument.h index 631e5f2315c..e232ee9ffca 100644 --- a/mozilla/editor/txtsvc/src/nsTextServicesDocument.h +++ b/mozilla/editor/txtsvc/src/nsTextServicesDocument.h @@ -51,6 +51,7 @@ #include "nsTSDNotifier.h" #include "nsISelectionController.h" #include "nsITextServicesFilter.h" +#include "nsWeakReference.h" class nsIWordBreaker; class nsIRangeUtils; @@ -97,7 +98,7 @@ private: nsCOMPtr mDOMDocument; nsCOMPtrmSelCon; - nsCOMPtr mEditor; + nsWeakPtr mEditor; // avoid a cycle with the spell checker and editor nsCOMPtr mIterator; TSDIteratorStatus mIteratorStatus; nsCOMPtr mPrevTextBlock; @@ -159,6 +160,7 @@ public: NS_IMETHOD DeleteSelection(); NS_IMETHOD InsertText(const nsString *aText); NS_IMETHOD SetDisplayStyle(TSDDisplayStyle aStyle); + NS_IMETHOD GetDOMRangeFor(PRInt32 aOffset, PRInt32 aLength, nsIDOMRange** aRange); /* nsIEditActionListener method implementations. */ nsresult InsertNode(nsIDOMNode * aNode, diff --git a/mozilla/editor/ui/composer/content/editorInlineSpellCheck.js b/mozilla/editor/ui/composer/content/editorInlineSpellCheck.js new file mode 100644 index 00000000000..0ded2d77613 --- /dev/null +++ b/mozilla/editor/ui/composer/content/editorInlineSpellCheck.js @@ -0,0 +1,177 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape 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/NPL/ + * + * 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 NPL, 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 NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +const kSpellMaxNumSuggestions = 7; // Maximum number of suggested words to fill into the context menu. Most + // applications (like Open Office) set this value to 15. + +const kSpellNoMispelling = -1; +const kSpellNoSuggestionsFound = 0; + +var InlineSpellChecker = +{ + editor : null, + inlineSpellChecker: null, + + Init : function (editor, enable) + { + this.editor = editor; + this.inlineSpellChecker = editor.inlineSpellChecker; + this.inlineSpellChecker.enableRealTimeSpell = enable; + }, + + checkDocument : function(doc) + { + if (!this.inlineSpellChecker || !this.inlineSpellChecker.enableRealTimeSpell) + return null; + + var range = doc.createRange(); + range.selectNodeContents(doc.body); + this.inlineSpellChecker.spellCheckRange(range); + }, + + getMispelledWord : function() + { + if (!this.inlineSpellChecker || !this.inlineSpellChecker.enableRealTimeSpell) + return null; + + var selection = this.editor.selection; + return this.inlineSpellChecker.getMispelledWord(selection.anchorNode, selection.anchorOffset); + }, + + // returns kSpellNoMispelling if the word is spelled correctly + // For mispelled words, returns kSpellNoSuggestionsFound when there are no suggestions otherwise the + // number of suggestions is returned. + // firstNonWordMenuItem is the first element in the menu popup that isn't a dynamically added word + // added by updateSuggestionsMenu. + updateSuggestionsMenu : function (menupopup, firstNonWordMenuItem, word) + { + if (!this.inlineSpellChecker || !this.inlineSpellChecker.enableRealTimeSpell) + return kSpellNoMispelling; + + var child = menupopup.firstChild; + while (child != firstNonWordMenuItem) + { + var next = child.nextSibling; + menupopup.removeChild(child); + child = next; + } + + if (!word) + { + word = this.getMispelledWord(); + if (!word) + return kSpellNoMispelling; + } + + var spellChecker = this.inlineSpellChecker.spellChecker; + if (!spellChecker) + return kSpellNoMispelling; + + var numSuggestedWords = 0; + + var isIncorrect = spellChecker.CheckCurrentWord(word.toString()); + if (isIncorrect) + { + do { + var suggestion = spellChecker.GetSuggestedWord(); + if (!suggestion) + break; + + var item = document.createElement("menuitem"); + item.setAttribute("label", suggestion); + item.setAttribute("value", suggestion); + + item.setAttribute("oncommand", "InlineSpellChecker.selectSuggestion(event.target.value, null, null);"); + menupopup.insertBefore(item, firstNonWordMenuItem); + numSuggestedWords++; + } while (numSuggestedWords < kSpellMaxNumSuggestions); + } + else + numSuggestedWords = kSpellNoMispelling; + + return numSuggestedWords; + }, + + selectSuggestion : function (newword, node, offset) + { + if (!this.inlineSpellChecker || !this.inlineSpellChecker.enableRealTimeSpell) + return null; + + if (!node) + { + var selection = this.editor.selection; + node = selection.anchorNode; + offset = selection.anchorOffset; + } + + this.inlineSpellChecker.replaceWord(node, offset, newword); + }, + + addToDictionary : function (node, offset) + { + if (!this.inlineSpellChecker || !this.inlineSpellChecker.enableRealTimeSpell) + return null; + + if (!node) + { + var selection = this.editor.selection; + node = selection.anchorNode; + offset = selection.anchorOffset; + } + + var word = this.inlineSpellChecker.getMispelledWord(node,offset); + if (word) this.inlineSpellChecker.addWordToDictionary(word); + }, + + ignoreWord : function (node, offset) + { + if (!this.inlineSpellChecker || !this.inlineSpellChecker.enableRealTimeSpell) + return null; + + if (!node) + { + var selection = this.editor.selection; + node = selection.anchorNode; + offset = selection.anchorOffset; + } + + var word = this.inlineSpellChecker.getMispelledWord(node, offset); + if (word) + this.inlineSpellChecker.ignoreWord(word); + } +} diff --git a/mozilla/editor/ui/jar.mn b/mozilla/editor/ui/jar.mn index d607283e4ce..9a3002b3e8d 100644 --- a/mozilla/editor/ui/jar.mn +++ b/mozilla/editor/ui/jar.mn @@ -25,6 +25,7 @@ comm.jar: content/editor/pref-composer.xul (composer/content/pref-composer.xul) content/editor/pref-publish.xul (composer/content/pref-publish.xul) content/editor/editorSmileyOverlay.xul (composer/content/editorSmileyOverlay.xul) + content/editor/editorInlineSpellCheck.js (composer/content/editorInlineSpellCheck.js) content/editor/editorPrefsOverlay.xul (composer/content/editorPrefsOverlay.xul) content/editor/editorNavigatorOverlay.xul (composer/content/editorNavigatorOverlay.xul) content/editor/editorMailOverlay.xul (composer/content/editorMailOverlay.xul)