michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "IMETextTxn.h" michael@0: #include "mozilla/DebugOnly.h" // for DebugOnly michael@0: #include "mozilla/mozalloc.h" // for operator new michael@0: #include "mozilla/TextEvents.h" // for TextRangeStyle michael@0: #include "nsAString.h" // for nsAString_internal::Length, etc michael@0: #include "nsAutoPtr.h" // for nsRefPtr michael@0: #include "nsDebug.h" // for NS_ASSERTION, etc michael@0: #include "nsError.h" // for NS_SUCCEEDED, NS_FAILED, etc michael@0: #include "nsIDOMCharacterData.h" // for nsIDOMCharacterData michael@0: #include "nsIDOMRange.h" // for nsRange::SetEnd, etc michael@0: #include "nsIContent.h" // for nsIContent michael@0: #include "nsIEditor.h" // for nsIEditor michael@0: #include "nsIPresShell.h" // for SelectionType michael@0: #include "nsISelection.h" // for nsISelection michael@0: #include "nsISelectionController.h" // for nsISelectionController, etc michael@0: #include "nsISelectionPrivate.h" // for nsISelectionPrivate michael@0: #include "nsISupportsImpl.h" // for nsRange::AddRef, etc michael@0: #include "nsISupportsUtils.h" // for NS_ADDREF_THIS, NS_RELEASE michael@0: #include "nsITransaction.h" // for nsITransaction michael@0: #include "nsRange.h" // for nsRange michael@0: #include "nsString.h" // for nsString michael@0: michael@0: using namespace mozilla; michael@0: michael@0: // #define DEBUG_IMETXN michael@0: michael@0: IMETextTxn::IMETextTxn() michael@0: : EditTxn() michael@0: { michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(IMETextTxn, EditTxn, michael@0: mElement) michael@0: // mRangeList can't lead to cycles michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMETextTxn) michael@0: if (aIID.Equals(IMETextTxn::GetCID())) { michael@0: *aInstancePtr = (void*)(IMETextTxn*)this; michael@0: NS_ADDREF_THIS(); michael@0: return NS_OK; michael@0: } else michael@0: NS_INTERFACE_MAP_END_INHERITING(EditTxn) michael@0: michael@0: NS_IMETHODIMP IMETextTxn::Init(nsIDOMCharacterData *aElement, michael@0: uint32_t aOffset, michael@0: uint32_t aReplaceLength, michael@0: TextRangeArray *aTextRangeArray, michael@0: const nsAString &aStringToInsert, michael@0: nsIEditor *aEditor) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aElement); michael@0: mElement = aElement; michael@0: mOffset = aOffset; michael@0: mReplaceLength = aReplaceLength; michael@0: mStringToInsert = aStringToInsert; michael@0: mEditor = aEditor; michael@0: mRanges = aTextRangeArray; michael@0: mFixed = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP IMETextTxn::DoTransaction(void) michael@0: { michael@0: michael@0: #ifdef DEBUG_IMETXN michael@0: printf("Do IME Text element = %p replace = %d len = %d\n", mElement.get(), mReplaceLength, mStringToInsert.Length()); michael@0: #endif michael@0: michael@0: nsCOMPtr selCon; michael@0: mEditor->GetSelectionController(getter_AddRefs(selCon)); michael@0: NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // advance caret: This requires the presentation shell to get the selection. michael@0: nsresult result; michael@0: if (mReplaceLength == 0) { michael@0: result = mElement->InsertData(mOffset, mStringToInsert); michael@0: } else { michael@0: result = mElement->ReplaceData(mOffset, mReplaceLength, mStringToInsert); michael@0: } michael@0: if (NS_SUCCEEDED(result)) { michael@0: result = SetSelectionForRanges(); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP IMETextTxn::UndoTransaction(void) michael@0: { michael@0: #ifdef DEBUG_IMETXN michael@0: printf("Undo IME Text element = %p\n", mElement.get()); michael@0: #endif michael@0: michael@0: nsCOMPtr selCon; michael@0: mEditor->GetSelectionController(getter_AddRefs(selCon)); michael@0: NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsresult result = mElement->DeleteData(mOffset, mStringToInsert.Length()); michael@0: if (NS_SUCCEEDED(result)) michael@0: { // set the selection to the insertion point where the string was removed michael@0: nsCOMPtr selection; michael@0: result = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); michael@0: if (NS_SUCCEEDED(result) && selection) { michael@0: result = selection->Collapse(mElement, mOffset); michael@0: NS_ASSERTION((NS_SUCCEEDED(result)), "selection could not be collapsed after undo of IME insert."); michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP IMETextTxn::Merge(nsITransaction *aTransaction, bool *aDidMerge) michael@0: { michael@0: NS_ASSERTION(aDidMerge, "illegal vaule- null ptr- aDidMerge"); michael@0: NS_ASSERTION(aTransaction, "illegal vaule- null ptr- aTransaction"); michael@0: NS_ENSURE_TRUE(aDidMerge && aTransaction, NS_ERROR_NULL_POINTER); michael@0: michael@0: #ifdef DEBUG_IMETXN michael@0: printf("Merge IME Text element = %p\n", mElement.get()); michael@0: #endif michael@0: michael@0: // michael@0: // check to make sure we aren't fixed, if we are then nothing get's absorbed michael@0: // michael@0: if (mFixed) { michael@0: *aDidMerge = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // if aTransaction is another IMETextTxn then absorb it michael@0: // michael@0: IMETextTxn* otherTxn = nullptr; michael@0: nsresult result = aTransaction->QueryInterface(IMETextTxn::GetCID(),(void**)&otherTxn); michael@0: if (otherTxn && NS_SUCCEEDED(result)) michael@0: { michael@0: // michael@0: // we absorb the next IME transaction by adopting its insert string as our own michael@0: // michael@0: mStringToInsert = otherTxn->mStringToInsert; michael@0: mRanges = otherTxn->mRanges; michael@0: *aDidMerge = true; michael@0: #ifdef DEBUG_IMETXN michael@0: printf("IMETextTxn assimilated IMETextTxn:%p\n", aTransaction); michael@0: #endif michael@0: NS_RELEASE(otherTxn); michael@0: return NS_OK; michael@0: } michael@0: michael@0: *aDidMerge = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP IMETextTxn::MarkFixed(void) michael@0: { michael@0: mFixed = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP IMETextTxn::GetTxnDescription(nsAString& aString) michael@0: { michael@0: aString.AssignLiteral("IMETextTxn: "); michael@0: aString += mStringToInsert; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* ============ protected methods ================== */ michael@0: static SelectionType michael@0: ToSelectionType(uint32_t aTextRangeType) michael@0: { michael@0: switch(aTextRangeType) { michael@0: case NS_TEXTRANGE_RAWINPUT: michael@0: return nsISelectionController::SELECTION_IME_RAWINPUT; michael@0: case NS_TEXTRANGE_SELECTEDRAWTEXT: michael@0: return nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT; michael@0: case NS_TEXTRANGE_CONVERTEDTEXT: michael@0: return nsISelectionController::SELECTION_IME_CONVERTEDTEXT; michael@0: case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT: michael@0: return nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT; michael@0: default: michael@0: MOZ_CRASH("Selection type is invalid"); michael@0: return nsISelectionController::SELECTION_NORMAL; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: IMETextTxn::SetSelectionForRanges() michael@0: { michael@0: nsCOMPtr selCon; michael@0: mEditor->GetSelectionController(getter_AddRefs(selCon)); michael@0: NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsCOMPtr selection; michael@0: nsresult rv = michael@0: selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, michael@0: getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr selPriv(do_QueryInterface(selection)); michael@0: rv = selPriv->StartBatchChanges(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // First, remove all selections of IME composition. michael@0: static const SelectionType kIMESelections[] = { michael@0: nsISelectionController::SELECTION_IME_RAWINPUT, michael@0: nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT, michael@0: nsISelectionController::SELECTION_IME_CONVERTEDTEXT, michael@0: nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT michael@0: }; michael@0: for (uint32_t i = 0; i < ArrayLength(kIMESelections); ++i) { michael@0: nsCOMPtr selectionOfIME; michael@0: if (NS_FAILED(selCon->GetSelection(kIMESelections[i], michael@0: getter_AddRefs(selectionOfIME)))) { michael@0: continue; michael@0: } michael@0: DebugOnly rv = selectionOfIME->RemoveAllRanges(); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), michael@0: "Failed to remove all ranges of IME selection"); michael@0: } michael@0: michael@0: // Set caret position and selection of IME composition with TextRangeArray. michael@0: bool setCaret = false; michael@0: uint32_t countOfRanges = mRanges ? mRanges->Length() : 0; michael@0: for (uint32_t i = 0; i < countOfRanges; ++i) { michael@0: const TextRange& textRange = mRanges->ElementAt(i); michael@0: michael@0: // Caret needs special handling since its length may be 0 and if it's not michael@0: // specified explicitly, we need to handle it ourselves later. michael@0: if (textRange.mRangeType == NS_TEXTRANGE_CARETPOSITION) { michael@0: NS_ASSERTION(!setCaret, "The ranges already has caret position"); michael@0: NS_ASSERTION(!textRange.Length(), "nsEditor doesn't support wide caret"); michael@0: // NOTE: If the caret position is larger than max length of the editor michael@0: // content, this may fail. michael@0: rv = selection->Collapse(mElement, mOffset + textRange.mStartOffset); michael@0: setCaret = setCaret || NS_SUCCEEDED(rv); michael@0: NS_ASSERTION(setCaret, "Failed to collapse normal selection"); michael@0: continue; michael@0: } michael@0: michael@0: // If the clause length is 0, it's should be a bug. michael@0: if (!textRange.Length()) { michael@0: NS_WARNING("Any clauses must not be empty"); michael@0: continue; michael@0: } michael@0: michael@0: nsRefPtr clauseRange; michael@0: rv = nsRange::CreateRange(mElement, mOffset + textRange.mStartOffset, michael@0: mElement, mOffset + textRange.mEndOffset, michael@0: getter_AddRefs(clauseRange)); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to create a DOM range for a clause of composition"); michael@0: break; michael@0: } michael@0: michael@0: // Set the range of the clause to selection. michael@0: nsCOMPtr selectionOfIME; michael@0: rv = selCon->GetSelection(ToSelectionType(textRange.mRangeType), michael@0: getter_AddRefs(selectionOfIME)); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to get IME selection"); michael@0: break; michael@0: } michael@0: michael@0: rv = selectionOfIME->AddRange(clauseRange); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to add selection range for a clause of composition"); michael@0: break; michael@0: } michael@0: michael@0: // Set the style of the clause. michael@0: nsCOMPtr selectionOfIMEPriv = michael@0: do_QueryInterface(selectionOfIME); michael@0: if (!selectionOfIMEPriv) { michael@0: NS_WARNING("Failed to get nsISelectionPrivate interface from selection"); michael@0: continue; // Since this is additional feature, we can continue this job. michael@0: } michael@0: rv = selectionOfIMEPriv->SetTextRangeStyle(clauseRange, michael@0: textRange.mRangeStyle); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to set selection style"); michael@0: break; // but this is unexpected... michael@0: } michael@0: } michael@0: michael@0: // If the ranges doesn't include explicit caret position, let's set the michael@0: // caret to the end of composition string. michael@0: if (!setCaret) { michael@0: rv = selection->Collapse(mElement, mOffset + mStringToInsert.Length()); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), michael@0: "Failed to set caret at the end of composition string"); michael@0: } michael@0: michael@0: rv = selPriv->EndBatchChanges(); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to end batch changes"); michael@0: michael@0: return rv; michael@0: } michael@0: