diff --git a/content/base/public/nsINode.h b/content/base/public/nsINode.h index 8ec0bc26620..780e260f7b4 100644 --- a/content/base/public/nsINode.h +++ b/content/base/public/nsINode.h @@ -48,6 +48,7 @@ class nsIContent; class nsIDocument; class nsIDOMEvent; +class nsIPresShell; class nsPresContext; class nsEventChainVisitor; class nsEventChainPreVisitor; @@ -59,6 +60,7 @@ class nsIMutationObserver; class nsChildContentList; class nsNodeWeakReference; class nsNodeSupportsWeakRefTearoff; +class nsIEditor; enum { // This bit will be set if the node doesn't have nsSlots @@ -144,8 +146,8 @@ inline nsINode* NODE_FROM(C& aContent, D& aDocument) // IID for the nsINode interface #define NS_INODE_IID \ -{ 0xd1c2e967, 0x854a, 0x436b, \ - { 0xbf, 0xa5, 0xf6, 0xa4, 0x9a, 0x97, 0x46, 0x74 } } +{ 0x6f69dd90, 0x318d, 0x40ac, \ + { 0xb8, 0xb8, 0x99, 0xb8, 0xa7, 0xbb, 0x9a, 0x58 } } /** * An internal interface that abstracts some DOMNode-related parts that both @@ -652,6 +654,22 @@ public: #endif } + /** + * Get the root content of an editor. So, this node must be a descendant of + * an editor. Note that this should be only used for getting input or textarea + * editor's root content. This method doesn't support HTML editors. + */ + nsIContent* GetTextEditorRootContent(nsIEditor** aEditor = nsnull); + + /** + * Get the nearest selection root, ie. the node that will be selected if the + * user does "Select All" while the focus is in this node. Note that if this + * node is not in an editor, the result comes from the nsFrameSelection that + * is related to aPresShell, so the result might not be the ancestor of this + * node. + */ + nsIContent* GetSelectionRootContent(nsIPresShell* aPresShell); + protected: // Override this function to create a custom slots class. diff --git a/content/base/src/Makefile.in b/content/base/src/Makefile.in index f1e627f8c39..0ea3c0fd47d 100644 --- a/content/base/src/Makefile.in +++ b/content/base/src/Makefile.in @@ -81,6 +81,7 @@ REQUIRES = xpcom \ util \ appshell \ shistory \ + editor \ $(NULL) ifdef ACCESSIBILITY diff --git a/content/base/src/contentbase.gqi b/content/base/src/contentbase.gqi index ee675301619..502f51822b6 100644 --- a/content/base/src/contentbase.gqi +++ b/content/base/src/contentbase.gqi @@ -18,7 +18,7 @@ %import-idl "nsIDOMXPathEvaluator.idl" %pseudo-iid nsIContent fba9aa39-016e-4d5d-ab62-22a1b84a3c7b -%pseudo-iid nsINode d1c2e967-854a-436b-bfa5-f6a49a974674 +%pseudo-iid nsINode 6f69dd90-318d-40ac-b8b8-99b8a7bb9a58 %pseudo-iid nsPIDOMEventTarget 44a6597b-9fc3-4a8d-b7a4-d9009abf9d15 %pseudo-iid nsIDocument 626d86d2-615f-4a12-94d8-e3db3a298372 %pseudo-iid nsIScriptObjectPrincipal 3eedba38-8d22-41e1-817a-0e43e165b664 diff --git a/content/base/src/nsGenericElement.cpp b/content/base/src/nsGenericElement.cpp index 56d198b4ef8..2d2a30ba4be 100644 --- a/content/base/src/nsGenericElement.cpp +++ b/content/base/src/nsGenericElement.cpp @@ -84,6 +84,7 @@ #include "nsXULElement.h" #endif /* MOZ_XUL */ #include "nsFrameManager.h" +#include "nsFrameSelection.h" #include "nsBindingManager.h" #include "nsXBLBinding.h" @@ -117,6 +118,9 @@ #include "nsIDOMNSFeatureFactory.h" #include "nsIDOMDocumentType.h" #include "nsIDOMUserDataHandler.h" +#include "nsIDOMNSEditableElement.h" +#include "nsIEditor.h" +#include "nsIEditorDocShell.h" #include "nsEventDispatcher.h" #include "nsContentCreatorFunctions.h" #include "nsIFocusController.h" @@ -311,6 +315,101 @@ nsINode::IsEditableInternal() const return doc && doc->HasFlag(NODE_IS_EDITABLE); } +static nsIContent* GetEditorRootContent(nsIEditor* aEditor) +{ + nsCOMPtr rootElement; + aEditor->GetRootElement(getter_AddRefs(rootElement)); + nsCOMPtr rootContent(do_QueryInterface(rootElement)); + return rootContent; +} + +nsIContent* +nsINode::GetTextEditorRootContent(nsIEditor** aEditor) +{ + if (aEditor) + *aEditor = nsnull; + for (nsINode* node = this; node; node = node->GetNodeParent()) { + nsCOMPtr editableElement(do_QueryInterface(node)); + if (!editableElement) + continue; + + nsCOMPtr editor; + nsresult rv = editableElement->GetEditor(getter_AddRefs(editor)); + NS_ENSURE_TRUE(editor, nsnull); + nsIContent* rootContent = GetEditorRootContent(editor); + if (aEditor) + editor.swap(*aEditor); + return rootContent; + } + return nsnull; +} + +static nsIEditor* GetHTMLEditor(nsPresContext* aPresContext) +{ + nsCOMPtr container = aPresContext->GetContainer(); + nsCOMPtr editorDocShell(do_QueryInterface(container)); + PRBool isEditable; + if (!editorDocShell || + NS_FAILED(editorDocShell->GetEditable(&isEditable)) || !isEditable) + return nsnull; + + nsCOMPtr editor; + editorDocShell->GetEditor(getter_AddRefs(editor)); + return editor; +} + +nsIContent* +nsINode::GetSelectionRootContent(nsIPresShell* aPresShell) +{ + NS_ENSURE_TRUE(aPresShell, nsnull); + + if (IsNodeOfType(eDOCUMENT)) + return static_cast(this)->GetRootContent(); + if (!IsNodeOfType(eCONTENT)) + return nsnull; + + nsIFrame* frame = + aPresShell->GetPrimaryFrameFor(static_cast(this)); + if (frame && frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION) { + // This node should be a descendant of input/textarea editor. + nsIContent* content = GetTextEditorRootContent(); + if (content) + return content; + NS_ERROR("Editor is not found!"); + } + + nsPresContext* presContext = aPresShell->GetPresContext(); + if (presContext) { + nsIEditor* editor = GetHTMLEditor(presContext); + if (editor) { + // This node is in HTML editor. + nsIDocument* doc = GetCurrentDoc(); + if (!doc || doc->HasFlag(NODE_IS_EDITABLE) || !HasFlag(NODE_IS_EDITABLE)) + return GetEditorRootContent(editor); + // If the current document is not editable, but current content is + // editable, we should assume that the child of the nearest non-editable + // ancestor is selection root. + nsIContent* content = static_cast(this); + for (nsIContent* parent = GetParent(); + parent && parent->HasFlag(NODE_IS_EDITABLE); + parent = content->GetParent()) + content = parent; + return content; + } + } + + nsCOMPtr fs = aPresShell->FrameSelection(); + nsIContent* content = fs->GetLimiter(); + if (content) + return content; + content = fs->GetAncestorLimiter(); + if (content) + return content; + nsIDocument* doc = aPresShell->GetDocument(); + NS_ENSURE_TRUE(doc, nsnull); + return doc->GetRootContent(); +} + //---------------------------------------------------------------------- PRInt32 diff --git a/content/events/src/Makefile.in b/content/events/src/Makefile.in index bd45ec83037..020ca63ac35 100644 --- a/content/events/src/Makefile.in +++ b/content/events/src/Makefile.in @@ -63,6 +63,7 @@ REQUIRES = xpcom \ necko \ unicharutil \ imglib2 \ + editor \ $(NULL) CPPSRCS = \ @@ -88,6 +89,7 @@ CPPSRCS = \ nsPLDOMEvent.cpp \ nsEventDispatcher.cpp \ nsIMEStateManager.cpp \ + nsQueryContentEventHandler.cpp \ $(NULL) # we don't want the shared lib, but we want to force the creation of a static lib. @@ -101,6 +103,8 @@ LOCAL_INCLUDES = \ -I$(srcdir)/../../xul/content/src \ -I$(srcdir)/../../xml/content/src \ -I$(srcdir)/../../../dom/src/base \ + -I$(srcdir)/../../../layout/generic \ + -I$(srcdir)/../../../layout/xul/base/src \ $(NULL) DEFINES += -D_IMPL_NS_LAYOUT diff --git a/content/events/src/nsEventStateManager.cpp b/content/events/src/nsEventStateManager.cpp index e176439219e..09a1710b9fd 100644 --- a/content/events/src/nsEventStateManager.cpp +++ b/content/events/src/nsEventStateManager.cpp @@ -46,6 +46,7 @@ #include "nsEventStateManager.h" #include "nsEventListenerManager.h" #include "nsIMEStateManager.h" +#include "nsQueryContentEventHandler.h" #include "nsIContent.h" #include "nsINodeInfo.h" #include "nsIDocument.h" @@ -1375,6 +1376,30 @@ nsEventStateManager::PreHandleEvent(nsPresContext* aPresContext, } } break; + case NS_QUERY_SELECTED_TEXT: + { + nsQueryContentEventHandler handler(mPresContext); + handler.OnQuerySelectedText((nsQueryContentEvent*)aEvent); + } + break; + case NS_QUERY_TEXT_CONTENT: + { + nsQueryContentEventHandler handler(mPresContext); + handler.OnQueryTextContent((nsQueryContentEvent*)aEvent); + } + break; + case NS_QUERY_CHARACTER_RECT: + { + nsQueryContentEventHandler handler(mPresContext); + handler.OnQueryCharacterRect((nsQueryContentEvent*)aEvent); + } + break; + case NS_QUERY_CARET_RECT: + { + nsQueryContentEventHandler handler(mPresContext); + handler.OnQueryCaretRect((nsQueryContentEvent*)aEvent); + } + break; } return NS_OK; } diff --git a/content/events/src/nsQueryContentEventHandler.cpp b/content/events/src/nsQueryContentEventHandler.cpp new file mode 100644 index 00000000000..33aa4f3b37a --- /dev/null +++ b/content/events/src/nsQueryContentEventHandler.cpp @@ -0,0 +1,575 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=80: */ +/* ***** 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 mozilla.org code. + * + * The Initial Developer of the Original Code is + * Mozilla Japan. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Masayuki Nakano + * + * 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"), + * 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 "nsQueryContentEventHandler.h" +#include "nsCOMPtr.h" +#include "nsPresContext.h" +#include "nsIPresShell.h" +#include "nsISelection.h" +#include "nsIDOMText.h" +#include "nsIDOMRange.h" +#include "nsRange.h" +#include "nsGUIEvent.h" +#include "nsICaret.h" +#include "nsFrameSelection.h" +#include "nsIFrame.h" +#include "nsIView.h" +#include "nsIContentIterator.h" +#include "nsTextFragment.h" +#include "nsTextFrame.h" + +nsresult NS_NewContentIterator(nsIContentIterator** aInstancePtrResult); + +/******************************************************************/ +/* nsQueryContentEventHandler */ +/******************************************************************/ + +nsQueryContentEventHandler::nsQueryContentEventHandler( + nsPresContext* aPresContext) : + mPresContext(aPresContext), + mPresShell(aPresContext->GetPresShell()), mSelection(nsnull), + mFirstSelectedRange(nsnull), mRootContent(nsnull) +{ +} + +nsresult +nsQueryContentEventHandler::Init(nsQueryContentEvent* aEvent) +{ + NS_ASSERTION(aEvent, "aEvent must not be null"); + + if (mSelection) + return NS_OK; + + aEvent->mSucceeded = PR_FALSE; + + if (!mPresShell) + return NS_ERROR_NOT_AVAILABLE; + + nsresult rv = mPresShell->GetSelectionForCopy(getter_AddRefs(mSelection)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(mSelection, + "GetSelectionForCopy succeeded, but the result is null"); + + nsCOMPtr firstRange; + rv = mSelection->GetRangeAt(0, getter_AddRefs(firstRange)); + // This shell doesn't support selection. + if (NS_FAILED(rv)) + return NS_ERROR_NOT_AVAILABLE; + mFirstSelectedRange = do_QueryInterface(firstRange); + NS_ENSURE_TRUE(mFirstSelectedRange, NS_ERROR_FAILURE); + + nsINode* startNode = mFirstSelectedRange->GetStartParent(); + NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); + mRootContent = startNode->GetSelectionRootContent(mPresShell); + NS_ENSURE_TRUE(mRootContent, NS_ERROR_FAILURE); + + aEvent->mReply.mContentsRoot = mRootContent.get(); + + return NS_OK; +} + +static void ConvertToNativeNewlines(nsAFlatString& aString) +{ +#if defined(XP_MACOSX) + aString.ReplaceSubstring(NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("\r")); +#elif defined(XP_WIN) + aString.ReplaceSubstring(NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("\r\n")); +#endif +} + +static void ConvertToXPNewlines(nsAFlatString& aString) +{ +#if defined(XP_MACOSX) + aString.ReplaceSubstring(NS_LITERAL_STRING("\r"), NS_LITERAL_STRING("\n")); +#elif defined(XP_WIN) + aString.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), NS_LITERAL_STRING("\n")); +#endif +} + +static void AppendString(nsAString& aString, nsIContent* aContent) +{ + NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), + "aContent is not a text node!"); + const nsTextFragment* text = aContent->GetText(); + if (!text) + return; + text->AppendTo(aString); +} + +static void AppendSubString(nsAString& aString, nsIContent* aContent, + PRUint32 aXPOffset, PRUint32 aXPLength) +{ + NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), + "aContent is not a text node!"); + const nsTextFragment* text = aContent->GetText(); + if (!text) + return; + text->AppendTo(aString, PRInt32(aXPOffset), PRInt32(aXPLength)); +} + +static PRUint32 GetNativeTextLength(nsIContent* aContent) +{ + nsAutoString str; + if (aContent->IsNodeOfType(nsINode::eTEXT)) + AppendString(str, aContent); + else if (aContent->IsNodeOfType(nsINode::eHTML) && + aContent->Tag() == nsGkAtoms::br) + str.Assign(PRUnichar('\n')); + ConvertToNativeNewlines(str); + return str.Length(); +} + +static PRUint32 ConvertToXPOffset(nsIContent* aContent, PRUint32 aNativeOffset) +{ + + nsAutoString str; + AppendString(str, aContent); + ConvertToNativeNewlines(str); + NS_ASSERTION(aNativeOffset <= str.Length(), + "aOffsetForNativeLF is too large!"); + str.Truncate(aNativeOffset); + ConvertToXPNewlines(str); + return str.Length(); +} + +nsresult +nsQueryContentEventHandler::GenerateFlatTextContent(nsIRange* aRange, + nsAFlatString& aString) +{ + nsCOMPtr iter; + nsresult rv = NS_NewContentIterator(getter_AddRefs(iter)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(iter, "NS_NewContentIterator succeeded, but the result is null"); + nsCOMPtr domRange(do_QueryInterface(aRange)); + NS_ASSERTION(domRange, "aRange doesn't have nsIDOMRange!"); + iter->Init(domRange); + + NS_ASSERTION(aString.IsEmpty(), "aString must be empty string"); + + nsINode* startNode = aRange->GetStartParent(); + nsINode* endNode = aRange->GetEndParent(); + + if (startNode == endNode && startNode->IsNodeOfType(nsINode::eTEXT)) { + nsIContent* content = static_cast(startNode); + AppendSubString(aString, content, aRange->StartOffset(), + aRange->EndOffset() - aRange->StartOffset()); + ConvertToNativeNewlines(aString); + return NS_OK; + } + + nsAutoString tmpStr; + for (; !iter->IsDone(); iter->Next()) { + nsIContent* content = iter->GetCurrentNode(); + if (!content) + continue; + + if (content->IsNodeOfType(nsINode::eTEXT)) { + if (content == startNode) + AppendSubString(aString, content, aRange->StartOffset(), + content->TextLength() - aRange->StartOffset()); + else if (content == endNode) + AppendSubString(aString, content, 0, aRange->EndOffset()); + else + AppendString(aString, content); + } else if (content->IsNodeOfType(nsINode::eHTML) && + content->Tag() == nsGkAtoms::br) + aString.Append(PRUnichar('\n')); + } + ConvertToNativeNewlines(aString); + return NS_OK; +} + +nsresult +nsQueryContentEventHandler::ExpandToClusterBoundary(nsIContent* aContent, + PRBool aForward, + PRUint32* aXPOffset) +{ + NS_ASSERTION(*aXPOffset >= 0 && *aXPOffset <= aContent->TextLength(), + "offset is out of range."); + + // XXX This method assumes that the frame boundaries must be cluster + // boundaries. It's false, but no problem now, maybe. + if (!aContent->IsNodeOfType(nsINode::eTEXT) || + *aXPOffset == 0 || *aXPOffset == aContent->TextLength()) + return NS_OK; + nsCOMPtr fs = mPresShell->FrameSelection(); + PRInt32 offsetInFrame; + nsFrameSelection::HINT hint = + aForward ? nsFrameSelection::HINTLEFT : nsFrameSelection::HINTRIGHT; + nsIFrame* frame = fs->GetFrameForNodeOffset(aContent, PRInt32(*aXPOffset), + hint, &offsetInFrame); + if (!frame) { + // This content doesn't have any frames, we only can check surrogate pair... + const nsTextFragment* text = aContent->GetText(); + NS_ENSURE_TRUE(text, NS_ERROR_FAILURE); + if (NS_IS_LOW_SURROGATE(text->CharAt(*aXPOffset)) && + NS_IS_HIGH_SURROGATE(text->CharAt(*aXPOffset - 1))) + *aXPOffset += aForward ? 1 : -1; + return NS_OK; + } + PRInt32 startOffset, endOffset; + nsresult rv = frame->GetOffsets(startOffset, endOffset); + NS_ENSURE_SUCCESS(rv, rv); + if (*aXPOffset == PRUint32(startOffset) || *aXPOffset == PRUint32(endOffset)) + return NS_OK; + if (frame->GetType() != nsGkAtoms::textFrame) + return NS_ERROR_FAILURE; + nsTextFrame* textFrame = static_cast(frame); + PRInt32 newOffsetInFrame = offsetInFrame; + newOffsetInFrame += aForward ? -1 : 1; + textFrame->PeekOffsetCharacter(aForward, &newOffsetInFrame); + *aXPOffset = startOffset + newOffsetInFrame; + return NS_OK; +} + +nsresult +nsQueryContentEventHandler::SetRangeFromFlatTextOffset( + nsIRange* aRange, + PRUint32 aNativeOffset, + PRUint32 aNativeLength, + PRBool aExpandToClusterBoundaries) +{ + nsCOMPtr iter; + nsresult rv = NS_NewContentIterator(getter_AddRefs(iter)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(iter, "NS_NewContentIterator succeeded, but the result is null"); + rv = iter->Init(mRootContent); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr domRange(do_QueryInterface(aRange)); + NS_ASSERTION(domRange, "aRange doesn't have nsIDOMRange!"); + + PRUint32 nativeOffset = 0; + PRUint32 nativeEndOffset = aNativeOffset + aNativeLength; + nsIContent* content = nsnull; + for (; !iter->IsDone(); iter->Next()) { + content = iter->GetCurrentNode(); + if (!content) + continue; + + PRUint32 nativeTextLength; + nativeTextLength = GetNativeTextLength(content); + if (nativeTextLength == 0) + continue; + + if (nativeOffset <= aNativeOffset && + aNativeOffset < nativeOffset + nativeTextLength) { + nsCOMPtr domNode(do_QueryInterface(content)); + NS_ASSERTION(domNode, "aContent doesn't have nsIDOMNode!"); + + PRUint32 xpOffset = + content->IsNodeOfType(nsINode::eTEXT) ? + ConvertToXPOffset(content, aNativeOffset - nativeOffset) : 0; + + if (aExpandToClusterBoundaries) { + rv = ExpandToClusterBoundary(content, PR_FALSE, &xpOffset); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = domRange->SetStart(domNode, PRInt32(xpOffset)); + NS_ENSURE_SUCCESS(rv, rv); + if (aNativeLength == 0) { + // Ensure that the end offset and the start offset are same. + rv = domRange->SetEnd(domNode, PRInt32(xpOffset)); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + } + if (nativeEndOffset <= nativeOffset + nativeTextLength) { + nsCOMPtr domNode(do_QueryInterface(content)); + NS_ASSERTION(domNode, "aContent doesn't have nsIDOMNode!"); + + PRUint32 xpOffset; + if (content->IsNodeOfType(nsINode::eTEXT)) { + xpOffset = ConvertToXPOffset(content, nativeEndOffset - nativeOffset); + if (aExpandToClusterBoundaries) { + rv = ExpandToClusterBoundary(content, PR_TRUE, &xpOffset); + NS_ENSURE_SUCCESS(rv, rv); + } + } else { + // Use first position of next node, because the end node is ignored + // by ContentIterator when the offset is zero. + xpOffset = 0; + iter->Next(); + if (iter->IsDone()) + break; + domNode = do_QueryInterface(iter->GetCurrentNode()); + } + + rv = domRange->SetEnd(domNode, PRInt32(xpOffset)); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + + nativeOffset += nativeTextLength; + } + + if (nativeOffset < aNativeOffset) + return NS_ERROR_FAILURE; + + nsCOMPtr domNode(do_QueryInterface(mRootContent)); + NS_ASSERTION(domNode, "lastContent doesn't have nsIDOMNode!"); + if (!content) { + rv = domRange->SetStart(domNode, 0); + NS_ENSURE_SUCCESS(rv, rv); + } + rv = domRange->SetEnd(domNode, PRInt32(mRootContent->GetChildCount())); + NS_ASSERTION(NS_SUCCEEDED(rv), "nsIDOMRange::SetEnd failed"); + return rv; +} + +nsresult +nsQueryContentEventHandler::OnQuerySelectedText(nsQueryContentEvent* aEvent) +{ + nsresult rv = Init(aEvent); + if (NS_FAILED(rv)) + return rv; + + NS_ASSERTION(aEvent->mReply.mString.IsEmpty(), + "The reply string must be empty"); + + rv = GetFlatTextOffsetOfRange(mFirstSelectedRange, &aEvent->mReply.mOffset); + NS_ENSURE_SUCCESS(rv, rv); + + PRBool isCollapsed; + rv = mSelection->GetIsCollapsed(&isCollapsed); + NS_ENSURE_SUCCESS(rv, rv); + + if (!isCollapsed) { + nsCOMPtr domRange; + rv = mSelection->GetRangeAt(0, getter_AddRefs(domRange)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(domRange, "GetRangeAt succeeded, but the result is null"); + + nsCOMPtr range(do_QueryInterface(domRange)); + NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); + rv = GenerateFlatTextContent(range, aEvent->mReply.mString); + NS_ENSURE_SUCCESS(rv, rv); + } + + aEvent->mSucceeded = PR_TRUE; + return NS_OK; +} + +nsresult +nsQueryContentEventHandler::OnQueryTextContent(nsQueryContentEvent* aEvent) +{ + nsresult rv = Init(aEvent); + if (NS_FAILED(rv)) + return rv; + + NS_ASSERTION(aEvent->mReply.mString.IsEmpty(), + "The reply string must be empty"); + + nsCOMPtr range = new nsRange(); + NS_ENSURE_TRUE(range, NS_ERROR_OUT_OF_MEMORY); + rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, + aEvent->mInput.mLength, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GenerateFlatTextContent(range, aEvent->mReply.mString); + NS_ENSURE_SUCCESS(rv, rv); + + aEvent->mSucceeded = PR_TRUE; + + return NS_OK; +} + +nsresult +nsQueryContentEventHandler::QueryRectFor(nsQueryContentEvent* aEvent, + nsIRange* aRange, + nsICaret* aCaret) +{ + PRInt32 offsetInFrame; + nsIFrame* frame; + nsresult rv = GetStartFrameAndOffset(aRange, &frame, &offsetInFrame); + NS_ENSURE_SUCCESS(rv, rv); + + nsPoint posInFrame; + rv = frame->GetPointFromOffset(aRange->StartOffset(), &posInFrame); + NS_ENSURE_SUCCESS(rv, rv); + + aEvent->mReply.mRect.y = posInFrame.y; + aEvent->mReply.mRect.height = frame->GetSize().height; + + if (aEvent->message == NS_QUERY_CHARACTER_RECT) { + nsPoint nextPos; + rv = frame->GetPointFromOffset(aRange->EndOffset(), &nextPos); + NS_ENSURE_SUCCESS(rv, rv); + aEvent->mReply.mRect.x = PR_MIN(posInFrame.x, nextPos.x); + aEvent->mReply.mRect.width = PR_ABS(posInFrame.x - nextPos.x); + } else { + aEvent->mReply.mRect.x = posInFrame.x; + aEvent->mReply.mRect.width = aCaret->GetCaretRect().width; + } + + // The coordinates are app units here, they will be converted to system + // coordinates by view manager. + rv = ConvertToRootViewRelativeOffset(frame, aEvent->mReply.mRect); + NS_ENSURE_SUCCESS(rv, rv); + + aEvent->mSucceeded = PR_TRUE; + return NS_OK; +} + +nsresult +nsQueryContentEventHandler::OnQueryCharacterRect(nsQueryContentEvent* aEvent) +{ + nsresult rv = Init(aEvent); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr range = new nsRange(); + NS_ENSURE_TRUE(range, NS_ERROR_OUT_OF_MEMORY); + rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, 1, PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); + + if (range->Collapsed()) { + // There is no character at the offset. + return NS_OK; + } + + return QueryRectFor(aEvent, range, nsnull); +} + +nsresult +nsQueryContentEventHandler::OnQueryCaretRect(nsQueryContentEvent* aEvent) +{ + nsresult rv = Init(aEvent); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr caret; + rv = mPresShell->GetCaret(getter_AddRefs(caret)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(caret, "GetCaret succeeded, but the result is null"); + + // When the selection is collapsed and the queried offset is current caret + // position, we should return the "real" caret rect. + PRBool selectionIsCollapsed; + rv = mSelection->GetIsCollapsed(&selectionIsCollapsed); + NS_ENSURE_SUCCESS(rv, rv); + + if (selectionIsCollapsed) { + PRUint32 offset; + rv = GetFlatTextOffsetOfRange(mFirstSelectedRange, &offset); + NS_ENSURE_SUCCESS(rv, rv); + if (offset == aEvent->mInput.mOffset) { + PRBool isCollapsed; + rv = caret->GetCaretCoordinates(nsICaret::eTopLevelWindowCoordinates, + mSelection, &aEvent->mReply.mRect, + &isCollapsed, nsnull); + NS_ENSURE_SUCCESS(rv, rv); + aEvent->mSucceeded = PR_TRUE; + return NS_OK; + } + } + + // Otherwise, we should set the guessed caret rect. + nsCOMPtr range = new nsRange(); + NS_ENSURE_TRUE(range, NS_ERROR_OUT_OF_MEMORY); + rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, 0, PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); + + return QueryRectFor(aEvent, range, caret); +} + +nsresult +nsQueryContentEventHandler::GetFlatTextOffsetOfRange(nsIRange* aRange, + PRUint32* aNativeOffset) +{ + NS_ASSERTION(aNativeOffset, "param is invalid"); + + nsCOMPtr prev = new nsRange(); + NS_ENSURE_TRUE(prev, NS_ERROR_OUT_OF_MEMORY); + nsCOMPtr domPrev(do_QueryInterface(prev)); + NS_ASSERTION(domPrev, "nsRange doesn't have nsIDOMRange??"); + nsCOMPtr rootDOMNode(do_QueryInterface(mRootContent)); + domPrev->SetStart(rootDOMNode, 0); + + nsINode* startNode = aRange->GetStartParent(); + NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); + + PRInt32 startOffset = aRange->StartOffset(); + nsCOMPtr startDOMNode(do_QueryInterface(startNode)); + NS_ASSERTION(startDOMNode, "startNode doesn't have nsIDOMNode"); + domPrev->SetEnd(startDOMNode, startOffset); + + nsAutoString prevStr; + nsresult rv = GenerateFlatTextContent(prev, prevStr); + NS_ENSURE_SUCCESS(rv, rv); + *aNativeOffset = prevStr.Length(); + return NS_OK; +} + +nsresult +nsQueryContentEventHandler::GetStartFrameAndOffset(nsIRange* aRange, + nsIFrame** aFrame, + PRInt32* aOffsetInFrame) +{ + NS_ASSERTION(aRange && aFrame && aOffsetInFrame, "params are invalid"); + + nsIContent* content = nsnull; + nsINode* node = aRange->GetStartParent(); + if (node && node->IsNodeOfType(nsINode::eCONTENT)) + content = static_cast(node); + NS_ASSERTION(content, "the start node doesn't have nsIContent!"); + + nsCOMPtr fs = mPresShell->FrameSelection(); + *aFrame = fs->GetFrameForNodeOffset(content, aRange->StartOffset(), + fs->GetHint(), aOffsetInFrame); + NS_ENSURE_TRUE((*aFrame), NS_ERROR_FAILURE); + NS_ASSERTION((*aFrame)->GetType() == nsGkAtoms::textFrame, + "The frame is not textframe"); + return NS_OK; +} + +nsresult +nsQueryContentEventHandler::ConvertToRootViewRelativeOffset(nsIFrame* aFrame, + nsRect& aRect) +{ + NS_ASSERTION(aFrame, "aFrame must not be null"); + + nsIView* view = nsnull; + nsPoint posInView; + aFrame->GetOffsetFromView(posInView, &view); + if (!view) + return NS_ERROR_FAILURE; + aRect += posInView + view->GetOffsetTo(nsnull); + return NS_OK; +} diff --git a/content/events/src/nsQueryContentEventHandler.h b/content/events/src/nsQueryContentEventHandler.h new file mode 100644 index 00000000000..e577bdff3e6 --- /dev/null +++ b/content/events/src/nsQueryContentEventHandler.h @@ -0,0 +1,115 @@ +/* -*- 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 mozilla.org code. + * + * The Initial Developer of the Original Code is + * Mozilla Japan. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Masayuki Nakano + * + * 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"), + * 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 nsQueryContentEventHandler_h__ +#define nsQueryContentEventHandler_h__ + +#include "nscore.h" +#include "nsCOMPtr.h" + +#include "nsISelection.h" +#include "nsIRange.h" +#include "nsIContent.h" +#include "nsIDOMTreeWalker.h" + +class nsPresContext; +class nsIPresShell; +class nsQueryContentEvent; +class nsICaret; +struct nsRect; + +/* + * Query Content Event Handler + * nsQueryContentEventHandler is a helper class for nsEventStateManager. + * The platforms request some content informations, e.g., the selected text, + * the offset of the selected text and the text for specified range. + * This class answers to NS_QUERY_* events from actual contents. + */ + +class nsQueryContentEventHandler { +public: + nsQueryContentEventHandler(nsPresContext *aPresContext); + + // NS_QUERY_SELECTED_TEXT event handler + nsresult OnQuerySelectedText(nsQueryContentEvent* aEvent); + // NS_QUERY_TEXT_CONTENT event handler + nsresult OnQueryTextContent(nsQueryContentEvent* aEvent); + // NS_QUERY_CHARACTER_RECT event handler + nsresult OnQueryCharacterRect(nsQueryContentEvent* aEvent); + // NS_QUERY_CARET_RECT event handler + nsresult OnQueryCaretRect(nsQueryContentEvent* aEvent); +protected: + nsPresContext* mPresContext; + nsIPresShell* mPresShell; + nsCOMPtr mSelection; + nsCOMPtr mFirstSelectedRange; + nsCOMPtr mRootContent; + + nsresult Init(nsQueryContentEvent* aEvent); + + // FlatText means the text that is generated from DOM tree. The BR elements + // are replaced to native linefeeds. Other elements are ignored. + + // Generate the FlatText from DOM range. + nsresult GenerateFlatTextContent(nsIRange* aRange, nsAFlatString& aString); + // Make the DOM range from the offset of FlatText and the text length. + // If aExpandToClusterBoundaries is true, the start offset and the end one are + // expanded to nearest cluster boundaries. + nsresult SetRangeFromFlatTextOffset(nsIRange* aRange, + PRUint32 aNativeOffset, + PRUint32 aNativeLength, + PRBool aExpandToClusterBoundaries); + // Get the offset in FlatText of the range. + nsresult GetFlatTextOffsetOfRange(nsIRange* aRange, PRUint32* aOffset); + // Find the first textframe for the range, and get the start offset in + // the frame. + nsresult GetStartFrameAndOffset(nsIRange* aRange, + nsIFrame** aFrame, PRInt32* aOffsetInFrame); + // Convert the frame relative offset to the root view relative offset. + nsresult ConvertToRootViewRelativeOffset(nsIFrame* aFrame, nsRect& aRect); + // The helper for OnQueryCharacterRect/OnQueryCaretRect. + // Don't call for another event. + nsresult QueryRectFor(nsQueryContentEvent* aEvent, nsIRange* aRange, + nsICaret* aCaret); + // Expand aXPOffset to the nearest offset in cluster boundary. aForward is + // true, it is expanded to forward. + nsresult ExpandToClusterBoundary(nsIContent* aContent, PRBool aForward, + PRUint32* aXPOffset); +}; + +#endif // nsQueryContentEventHandler_h__ diff --git a/editor/libeditor/html/nsHTMLEditor.cpp b/editor/libeditor/html/nsHTMLEditor.cpp index 9c3b875f7b8..1e707c5395d 100644 --- a/editor/libeditor/html/nsHTMLEditor.cpp +++ b/editor/libeditor/html/nsHTMLEditor.cpp @@ -4232,23 +4232,6 @@ nsHTMLEditor::SelectEntireDocument(nsISelection *aSelection) return nsEditor::SelectEntireDocument(aSelection); } -static nsIContent* -FindEditableRoot(nsIContent *aContent) -{ - nsIDocument *document = aContent->GetCurrentDoc(); - if (!document || document->HasFlag(NODE_IS_EDITABLE) || - !aContent->HasFlag(NODE_IS_EDITABLE)) { - return nsnull; - } - - nsIContent *parent, *content = aContent; - while ((parent = content->GetParent()) && parent->HasFlag(NODE_IS_EDITABLE)) { - content = parent; - } - - return content; -} - NS_IMETHODIMP nsHTMLEditor::SelectAll() { @@ -4270,10 +4253,9 @@ nsHTMLEditor::SelectAll() nsCOMPtr anchorContent = do_QueryInterface(anchorNode, &rv); NS_ENSURE_SUCCESS(rv, rv); - nsIContent *rootContent = FindEditableRoot(anchorContent); - if (!rootContent) { - return SelectEntireDocument(selection); - } + nsCOMPtr ps = do_QueryReferent(mPresShellWeak); + nsIContent *rootContent = anchorContent->GetSelectionRootContent(ps); + NS_ASSERTION(rootContent, "GetSelectionRootContent failed"); nsCOMPtr rootElement = do_QueryInterface(rootContent, &rv); NS_ENSURE_SUCCESS(rv, rv); diff --git a/view/src/nsViewManager.cpp b/view/src/nsViewManager.cpp index f484d2739fe..5b9845281f7 100644 --- a/view/src/nsViewManager.cpp +++ b/view/src/nsViewManager.cpp @@ -994,6 +994,14 @@ void nsViewManager::UnsuppressFocusEvents() } +static void ConvertRectAppUnitsToIntPixels(nsRect& aRect, PRInt32 p2a) +{ + aRect.x = NSAppUnitsToIntPixels(aRect.x, p2a); + aRect.y = NSAppUnitsToIntPixels(aRect.y, p2a); + aRect.width = NSAppUnitsToIntPixels(aRect.width, p2a); + aRect.height = NSAppUnitsToIntPixels(aRect.height, p2a); +} + NS_IMETHODIMP nsViewManager::DispatchEvent(nsGUIEvent *aEvent, nsEventStatus *aStatus) { *aStatus = nsEventStatus_eIgnore; @@ -1240,6 +1248,7 @@ NS_IMETHODIMP nsViewManager::DispatchEvent(nsGUIEvent *aEvent, nsEventStatus *aS if (!NS_IS_KEY_EVENT(aEvent) && !NS_IS_IME_EVENT(aEvent) && !NS_IS_CONTEXT_MENU_KEY(aEvent) && !NS_IS_FOCUS_EVENT(aEvent) && + !NS_IS_QUERY_CONTENT_EVENT(aEvent) && aEvent->eventStructType != NS_ACCESSIBLE_EVENT) { // will dispatch using coordinates. Pretty bogus but it's consistent // with what presshell does. @@ -1326,26 +1335,27 @@ NS_IMETHODIMP nsViewManager::DispatchEvent(nsGUIEvent *aEvent, nsEventStatus *aS *aStatus = HandleEvent(view, pt, aEvent, capturedEvent); // - // if the event is an nsTextEvent, we need to map the reply back into platform coordinates + // need to map the reply back into platform coordinates // - if (aEvent->message==NS_TEXT_TEXT) { - ((nsTextEvent*)aEvent)->theReply.mCursorPosition.x=NSAppUnitsToIntPixels(((nsTextEvent*)aEvent)->theReply.mCursorPosition.x, p2a); - ((nsTextEvent*)aEvent)->theReply.mCursorPosition.y=NSAppUnitsToIntPixels(((nsTextEvent*)aEvent)->theReply.mCursorPosition.y, p2a); - ((nsTextEvent*)aEvent)->theReply.mCursorPosition.width=NSAppUnitsToIntPixels(((nsTextEvent*)aEvent)->theReply.mCursorPosition.width, p2a); - ((nsTextEvent*)aEvent)->theReply.mCursorPosition.height=NSAppUnitsToIntPixels(((nsTextEvent*)aEvent)->theReply.mCursorPosition.height, p2a); - } - if((aEvent->message==NS_COMPOSITION_START) || - (aEvent->message==NS_COMPOSITION_QUERY)) { - ((nsCompositionEvent*)aEvent)->theReply.mCursorPosition.x=NSAppUnitsToIntPixels(((nsCompositionEvent*)aEvent)->theReply.mCursorPosition.x, p2a); - ((nsCompositionEvent*)aEvent)->theReply.mCursorPosition.y=NSAppUnitsToIntPixels(((nsCompositionEvent*)aEvent)->theReply.mCursorPosition.y, p2a); - ((nsCompositionEvent*)aEvent)->theReply.mCursorPosition.width=NSAppUnitsToIntPixels(((nsCompositionEvent*)aEvent)->theReply.mCursorPosition.width, p2a); - ((nsCompositionEvent*)aEvent)->theReply.mCursorPosition.height=NSAppUnitsToIntPixels(((nsCompositionEvent*)aEvent)->theReply.mCursorPosition.height, p2a); - } - if(aEvent->message==NS_QUERYCARETRECT) { - ((nsQueryCaretRectEvent*)aEvent)->theReply.mCaretRect.x=NSAppUnitsToIntPixels(((nsQueryCaretRectEvent*)aEvent)->theReply.mCaretRect.x, p2a); - ((nsQueryCaretRectEvent*)aEvent)->theReply.mCaretRect.y=NSAppUnitsToIntPixels(((nsQueryCaretRectEvent*)aEvent)->theReply.mCaretRect.y, p2a); - ((nsQueryCaretRectEvent*)aEvent)->theReply.mCaretRect.width=NSAppUnitsToIntPixels(((nsQueryCaretRectEvent*)aEvent)->theReply.mCaretRect.width, p2a); - ((nsQueryCaretRectEvent*)aEvent)->theReply.mCaretRect.height=NSAppUnitsToIntPixels(((nsQueryCaretRectEvent*)aEvent)->theReply.mCaretRect.height, p2a); + switch (aEvent->message) { + case NS_TEXT_TEXT: + ConvertRectAppUnitsToIntPixels( + ((nsTextEvent*)aEvent)->theReply.mCursorPosition, p2a); + break; + case NS_COMPOSITION_START: + case NS_COMPOSITION_QUERY: + ConvertRectAppUnitsToIntPixels( + ((nsCompositionEvent*)aEvent)->theReply.mCursorPosition, p2a); + break; + case NS_QUERYCARETRECT: + ConvertRectAppUnitsToIntPixels( + ((nsQueryCaretRectEvent*)aEvent)->theReply.mCaretRect, p2a); + break; + case NS_QUERY_CHARACTER_RECT: + case NS_QUERY_CARET_RECT: + ConvertRectAppUnitsToIntPixels( + ((nsQueryContentEvent*)aEvent)->mReply.mRect, p2a); + break; } } diff --git a/widget/public/nsGUIEvent.h b/widget/public/nsGUIEvent.h index c2ea0b99a6a..f2c2010f011 100644 --- a/widget/public/nsGUIEvent.h +++ b/widget/public/nsGUIEvent.h @@ -102,6 +102,7 @@ class nsHashKey; #define NS_SVGZOOM_EVENT 31 #endif // MOZ_SVG #define NS_XUL_COMMAND_EVENT 32 +#define NS_QUERY_CONTENT_EVENT 33 // These flags are sort of a mess. They're sort of shared between event // listener flags and event flags, but only some of them. You've been @@ -344,6 +345,22 @@ class nsHashKey; #define NS_BEFORECUT (NS_CUTCOPYPASTE_EVENT_START + 4) #define NS_BEFOREPASTE (NS_CUTCOPYPASTE_EVENT_START + 5) +// Query the content information +#define NS_QUERY_CONTENT_EVENT_START 3200 +// Query for the selected text information, it return the selection offset, +// selection length and selected text. +#define NS_QUERY_SELECTED_TEXT (NS_QUERY_CONTENT_EVENT_START) +// Query for the text content of specified range, it returns actual lengh (if +// the specified range is too long) and the text of the specified range. +#define NS_QUERY_TEXT_CONTENT (NS_QUERY_CONTENT_EVENT_START + 1) +// Query for the character rect of nth character. If there is no character at +// the offset, the query will be failed. The offset of the result is relative +// position from the top level widget. +#define NS_QUERY_CHARACTER_RECT (NS_QUERY_CONTENT_EVENT_START + 2) +// Query for the caret rect of nth insertion point. The offset of the result is +// relative position from the top level widget. +#define NS_QUERY_CARET_RECT (NS_QUERY_CONTENT_EVENT_START + 3) + /** * Return status for event processors, nsEventStatus, is defined in * nsEvent.h. @@ -823,6 +840,50 @@ public: nsQueryCaretRectEventReply theReply; }; +class nsQueryContentEvent : public nsGUIEvent +{ +public: + nsQueryContentEvent(PRBool aIsTrusted, PRUint32 aMsg, nsIWidget *aWidget) : + nsGUIEvent(aIsTrusted, aMsg, aWidget, NS_QUERY_CONTENT_EVENT), + mSucceeded(PR_FALSE) + { + } + + void InitForQueryTextContent(PRUint32 aOffset, PRUint32 aLength) + { + NS_ASSERTION(message == NS_QUERY_TEXT_CONTENT, + "wrong initializer is called"); + mInput.mOffset = aOffset; + mInput.mLength = aLength; + } + + void InitForQueryCharacterRect(PRUint32 aOffset) + { + NS_ASSERTION(message == NS_QUERY_CHARACTER_RECT, + "wrong initializer is called"); + mInput.mOffset = aOffset; + } + + void InitForQueryCaretRect(PRUint32 aOffset) + { + NS_ASSERTION(message == NS_QUERY_CARET_RECT, + "wrong initializer is called"); + mInput.mOffset = aOffset; + } + + PRBool mSucceeded; + struct { + PRUint32 mOffset; + PRUint32 mLength; + } mInput; + struct { + void* mContentsRoot; + PRUint32 mOffset; + nsString mString; + nsRect mRect; // Finally, the coordinates is system coordinates. + } mReply; +}; + /** * MenuItem event * @@ -1023,6 +1084,12 @@ enum nsDragDropEventStatus { ((evnt)->message == NS_DEACTIVATE) || \ ((evnt)->message == NS_PLUGIN_ACTIVATE)) +#define NS_IS_QUERY_CONTENT_EVENT(evnt) \ + (((evnt)->message == NS_QUERY_SELECTED_TEXT) || \ + ((evnt)->message == NS_QUERY_TEXT_CONTENT) || \ + ((evnt)->message == NS_QUERY_CHARACTER_RECT) || \ + ((evnt)->message == NS_QUERY_CARET_RECT)) + #define NS_IS_TRUSTED_EVENT(event) \ (((event)->flags & NS_EVENT_FLAG_TRUSTED) != 0) diff --git a/widget/src/cocoa/nsChildView.h b/widget/src/cocoa/nsChildView.h index 61549356f2e..33b71202d38 100644 --- a/widget/src/cocoa/nsChildView.h +++ b/widget/src/cocoa/nsChildView.h @@ -96,7 +96,6 @@ union nsPluginPort; // needed for NSTextInput implementation NSRange mMarkedRange; - NSRange mSelectedRange; BOOL mIgnoreDoCommand; BOOL mInHandScroll; // true for as long as we are hand scrolling @@ -240,7 +239,8 @@ public: NS_IMETHOD IsVisible(PRBool& outState); virtual nsIWidget* GetParent(void); - + nsIWidget* GetTopLevelWidget(); + NS_IMETHOD ModalEventFilter(PRBool aRealEvent, void *aEvent, PRBool *aForWindow); diff --git a/widget/src/cocoa/nsChildView.mm b/widget/src/cocoa/nsChildView.mm index 8f7d76f9165..d44a49e5988 100644 --- a/widget/src/cocoa/nsChildView.mm +++ b/widget/src/cocoa/nsChildView.mm @@ -734,6 +734,14 @@ nsChildView::GetParent(void) return mParentWidget; } +nsIWidget* +nsChildView::GetTopLevelWidget() +{ + nsIWidget* current = this; + for (nsIWidget* parent = GetParent(); parent ; parent = parent->GetParent()) + current = parent; + return current; +} NS_IMETHODIMP nsChildView::ModalEventFilter(PRBool aRealEvent, void *aEvent, PRBool *aForWindow) @@ -1853,8 +1861,6 @@ NSEvent* gLastDragEvent = nil; // initialization for NSTextInput mMarkedRange.location = NSNotFound; mMarkedRange.length = 0; - mSelectedRange.location = NSNotFound; - mSelectedRange.length = 0; mIgnoreDoCommand = NO; mLastMenuForEventEvent = nil; mDragService = nsnull; @@ -3590,7 +3596,7 @@ static PRBool IsSpecialGeckoKey(UInt32 macKeyCode) { #if DEBUG_IME NSLog(@"****in insertText: '%@'", insertString); - NSLog(@" markRange = %d, %d; selRange = %d, %d", mMarkedRange.location, mMarkedRange.length, mSelectedRange.location, mSelectedRange.length); + NSLog(@" markRange = %d, %d", mMarkedRange.location, mMarkedRange.length); #endif if (!mGeckoChild) return; @@ -3671,7 +3677,7 @@ static PRBool IsSpecialGeckoKey(UInt32 macKeyCode) [self sendCompositionEvent:NS_COMPOSITION_END]; // Note: mGeckoChild might have become null here. Don't count on it from here on. nsTSMManager::EndComposing(); - mSelectedRange = mMarkedRange = NSMakeRange(NSNotFound, 0); + mMarkedRange = NSMakeRange(NSNotFound, 0); } if (bufPtr != buffer) @@ -3708,7 +3714,7 @@ static PRBool IsSpecialGeckoKey(UInt32 macKeyCode) { #if DEBUG_IME NSLog(@"****in setMarkedText location: %d, length: %d", selRange.location, selRange.length); - NSLog(@" markRange = %d, %d; selRange = %d, %d", mMarkedRange.location, mMarkedRange.length, mSelectedRange.location, mSelectedRange.length); + NSLog(@" markRange = %d, %d", mMarkedRange.location, mMarkedRange.length); NSLog(@" aString = '%@'", aString); #endif @@ -3718,8 +3724,6 @@ static PRBool IsSpecialGeckoKey(UInt32 macKeyCode) if (![aString isKindOfClass:[NSAttributedString class]]) aString = [[[NSAttributedString alloc] initWithString:aString] autorelease]; - mSelectedRange = selRange; - NSMutableAttributedString *mutableAttribStr = aString; NSString *tmpStr = [mutableAttribStr string]; unsigned int len = [tmpStr length]; @@ -3732,14 +3736,17 @@ static PRBool IsSpecialGeckoKey(UInt32 macKeyCode) printf("****in setMarkedText, len = %d, text = ", len); PRUint32 n = 0; PRUint32 maxlen = len > 12 ? 12 : len; - for (PRUnichar *a = bufPtr; (*a != PRUnichar('\0')) && nDispatchWindowEvent(selection); + mMarkedRange.location = selection.mSucceeded ? selection.mReply.mOffset : 0; [self sendCompositionEvent:NS_COMPOSITION_START]; // Note: mGeckoChild might have become null here. Don't count on it from here on. nsTSMManager::StartComposing(self); @@ -3771,7 +3778,6 @@ static PRBool IsSpecialGeckoKey(UInt32 macKeyCode) #if DEBUG_IME NSLog(@"****in unmarkText"); NSLog(@" markedRange = %d, %d", mMarkedRange.location, mMarkedRange.length); - NSLog(@" selectedRange = %d, %d", mSelectedRange.location, mSelectedRange.length); #endif nsTSMManager::CommitIME(); } @@ -3779,13 +3785,30 @@ static PRBool IsSpecialGeckoKey(UInt32 macKeyCode) - (BOOL) hasMarkedText { +#if DEBUG_IME + NSLog(@"****in hasMarkText"); + NSLog(@" markedRange = %d, %d", mMarkedRange.location, mMarkedRange.length); +#endif return (mMarkedRange.location != NSNotFound) && (mMarkedRange.length != 0); } - (long) conversationIdentifier { - return (long)self; +#if DEBUG_IME + NSLog(@"****in conversationIdentifier"); +#endif + if (!mGeckoChild) + return (long)self; + nsQueryContentEvent textContent(PR_TRUE, NS_QUERY_TEXT_CONTENT, mGeckoChild); + textContent.InitForQueryTextContent(0, 0); + mGeckoChild->DispatchWindowEvent(textContent); + if (!textContent.mSucceeded) + return (long)self; +#if DEBUG_IME + NSLog(@" the ID = %ld", (long)textContent.mReply.mContentsRoot); +#endif + return (long)textContent.mReply.mContentsRoot; } @@ -3795,24 +3818,25 @@ static PRBool IsSpecialGeckoKey(UInt32 macKeyCode) NSLog(@"****in attributedSubstringFromRange"); NSLog(@" theRange = %d, %d", theRange.location, theRange.length); NSLog(@" markedRange = %d, %d", mMarkedRange.location, mMarkedRange.length); - NSLog(@" selectedRange = %d, %d", mSelectedRange.location, mSelectedRange.length); #endif - if (!mGeckoChild) + if (!mGeckoChild || theRange.length == 0) return nil; - nsReconversionEvent reconversionEvent(PR_TRUE, NS_RECONVERSION_QUERY, mGeckoChild); - reconversionEvent.time = PR_IntervalNow(); + nsAutoString str; + nsQueryContentEvent textContent(PR_TRUE, NS_QUERY_TEXT_CONTENT, mGeckoChild); + textContent.InitForQueryTextContent(theRange.location, theRange.length); + mGeckoChild->DispatchWindowEvent(textContent); - nsresult rv = mGeckoChild->DispatchWindowEvent(reconversionEvent); - PRUnichar* reconvstr; - if (NS_SUCCEEDED(rv) && (reconvstr = reconversionEvent.theReply.mReconversionString)) { - NSAttributedString* result = [[[NSAttributedString alloc] initWithString:[NSString stringWithCharacters:reconvstr length:nsCRT::strlen(reconvstr)] - attributes:nil] autorelease]; - nsMemory::Free(reconvstr); - return result; - } + if (!textContent.mSucceeded || textContent.mReply.mString.IsEmpty()) + return nil; - return nil; + NSString* nsstr = + [NSString stringWithCharacters:textContent.mReply.mString.get() + length:textContent.mReply.mString.Length()]; + NSAttributedString* result = + [[[NSAttributedString alloc] initWithString:nsstr + attributes:nil] autorelease]; + return result; } @@ -3821,7 +3845,6 @@ static PRBool IsSpecialGeckoKey(UInt32 macKeyCode) #if DEBUG_IME NSLog(@"****in markedRange"); NSLog(@" markedRange = %d, %d", mMarkedRange.location, mMarkedRange.length); - NSLog(@" selectedRange = %d, %d", mSelectedRange.location, mSelectedRange.length); #endif if (![self hasMarkedText]) { @@ -3837,10 +3860,20 @@ static PRBool IsSpecialGeckoKey(UInt32 macKeyCode) #if DEBUG_IME NSLog(@"****in selectedRange"); NSLog(@" markedRange = %d, %d", mMarkedRange.location, mMarkedRange.length); - NSLog(@" selectedRange = %d, %d", mSelectedRange.location, mSelectedRange.length); #endif + if (!mGeckoChild) + return NSMakeRange(NSNotFound, 0); + nsQueryContentEvent selection(PR_TRUE, NS_QUERY_SELECTED_TEXT, mGeckoChild); + mGeckoChild->DispatchWindowEvent(selection); + if (!selection.mSucceeded) + return NSMakeRange(NSNotFound, 0); - return mSelectedRange; +#if DEBUG_IME + NSLog(@" result of selectedRange = %d, %d", + selection.mReply.mOffset, selection.mReply.mString.Length()); +#endif + return NSMakeRange(selection.mReply.mOffset, + selection.mReply.mString.Length()); } @@ -3850,20 +3883,52 @@ static PRBool IsSpecialGeckoKey(UInt32 macKeyCode) NSLog(@"****in firstRectForCharacterRange"); NSLog(@" theRange = %d, %d", theRange.location, theRange.length); NSLog(@" markedRange = %d, %d", mMarkedRange.location, mMarkedRange.length); - NSLog(@" selectedRange = %d, %d", mSelectedRange.location, mSelectedRange.length); #endif + // XXX this returns first character rect or caret rect, it is limitation of + // now. We need more work for returns first line rect. But current + // implementation is enough for IMEs. - nsAutoRetainView kungFuDeathGrip(self); - nsRect compositionRect = [self sendCompositionEvent:NS_COMPOSITION_QUERY]; + NSRect rect; + if (!mGeckoChild || theRange.location == NSNotFound) + return rect; - NSRect rangeRect; - GeckoRectToNSRect(compositionRect, rangeRect); + nsRect r; + PRBool useCaretRect = theRange.length == 0; + if (!useCaretRect) { + nsQueryContentEvent charRect(PR_TRUE, NS_QUERY_CHARACTER_RECT, mGeckoChild); + charRect.InitForQueryCharacterRect(theRange.location); + mGeckoChild->DispatchWindowEvent(charRect); + if (charRect.mSucceeded) + r = charRect.mReply.mRect; + else + useCaretRect = PR_TRUE; + } - // convert to window coords - rangeRect = [self convertRect:rangeRect toView:nil]; - // convert to cocoa screen coords - rangeRect.origin = [[self nativeWindow] convertBaseToScreen:rangeRect.origin]; - return rangeRect; + if (useCaretRect) { + nsQueryContentEvent caretRect(PR_TRUE, NS_QUERY_CARET_RECT, mGeckoChild); + caretRect.InitForQueryCaretRect(theRange.location); + mGeckoChild->DispatchWindowEvent(caretRect); + if (!caretRect.mSucceeded) + return rect; + r = caretRect.mReply.mRect; + r.width = 0; + } + + nsIWidget* rootWidget = mGeckoChild->GetTopLevelWidget(); + NSWindow* rootWindow = + static_cast(rootWidget->GetNativeData(NS_NATIVE_WINDOW)); + NSView* rootView = + static_cast(rootWidget->GetNativeData(NS_NATIVE_WIDGET)); + if (!rootWindow || !rootView) + return rect; + GeckoRectToNSRect(r, rect); + rect = [rootView convertRect:rect toView:nil]; + rect.origin = [rootWindow convertBaseToScreen:rect.origin]; +#if DEBUG_IME + NSLog(@" result rect (x,y,w,h) = %f, %f, %f, %f", + rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); +#endif + return rect; } @@ -3871,7 +3936,7 @@ static PRBool IsSpecialGeckoKey(UInt32 macKeyCode) { #if DEBUG_IME NSLog(@"****in characterIndexForPoint"); - NSLog(@" markRange = %d, %d; selectRange = %d, %d", mMarkedRange.location, mMarkedRange.length, mSelectedRange.location, mSelectedRange.length); + NSLog(@" markRange = %d, %d", mMarkedRange.location, mMarkedRange.length); #endif // To implement this, we'd have to grovel in text frames looking at text offsets. @@ -3883,7 +3948,7 @@ static PRBool IsSpecialGeckoKey(UInt32 macKeyCode) { #if DEBUG_IME NSLog(@"****in validAttributesForMarkedText"); - NSLog(@" markRange = %d, %d; selectRange = %d, %d", mMarkedRange.location, mMarkedRange.length, mSelectedRange.location, mSelectedRange.length); + NSLog(@" markRange = %d, %d", mMarkedRange.location, mMarkedRange.length); #endif //return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName, NSMarkedClauseSegmentAttributeName, NSTextInputReplacementRangeAttributeName, nil];