1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/editor/libeditor/base/IMETextTxn.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,301 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "IMETextTxn.h" 1.10 +#include "mozilla/DebugOnly.h" // for DebugOnly 1.11 +#include "mozilla/mozalloc.h" // for operator new 1.12 +#include "mozilla/TextEvents.h" // for TextRangeStyle 1.13 +#include "nsAString.h" // for nsAString_internal::Length, etc 1.14 +#include "nsAutoPtr.h" // for nsRefPtr 1.15 +#include "nsDebug.h" // for NS_ASSERTION, etc 1.16 +#include "nsError.h" // for NS_SUCCEEDED, NS_FAILED, etc 1.17 +#include "nsIDOMCharacterData.h" // for nsIDOMCharacterData 1.18 +#include "nsIDOMRange.h" // for nsRange::SetEnd, etc 1.19 +#include "nsIContent.h" // for nsIContent 1.20 +#include "nsIEditor.h" // for nsIEditor 1.21 +#include "nsIPresShell.h" // for SelectionType 1.22 +#include "nsISelection.h" // for nsISelection 1.23 +#include "nsISelectionController.h" // for nsISelectionController, etc 1.24 +#include "nsISelectionPrivate.h" // for nsISelectionPrivate 1.25 +#include "nsISupportsImpl.h" // for nsRange::AddRef, etc 1.26 +#include "nsISupportsUtils.h" // for NS_ADDREF_THIS, NS_RELEASE 1.27 +#include "nsITransaction.h" // for nsITransaction 1.28 +#include "nsRange.h" // for nsRange 1.29 +#include "nsString.h" // for nsString 1.30 + 1.31 +using namespace mozilla; 1.32 + 1.33 +// #define DEBUG_IMETXN 1.34 + 1.35 +IMETextTxn::IMETextTxn() 1.36 + : EditTxn() 1.37 +{ 1.38 +} 1.39 + 1.40 +NS_IMPL_CYCLE_COLLECTION_INHERITED(IMETextTxn, EditTxn, 1.41 + mElement) 1.42 +// mRangeList can't lead to cycles 1.43 + 1.44 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMETextTxn) 1.45 + if (aIID.Equals(IMETextTxn::GetCID())) { 1.46 + *aInstancePtr = (void*)(IMETextTxn*)this; 1.47 + NS_ADDREF_THIS(); 1.48 + return NS_OK; 1.49 + } else 1.50 +NS_INTERFACE_MAP_END_INHERITING(EditTxn) 1.51 + 1.52 +NS_IMETHODIMP IMETextTxn::Init(nsIDOMCharacterData *aElement, 1.53 + uint32_t aOffset, 1.54 + uint32_t aReplaceLength, 1.55 + TextRangeArray *aTextRangeArray, 1.56 + const nsAString &aStringToInsert, 1.57 + nsIEditor *aEditor) 1.58 +{ 1.59 + NS_ENSURE_ARG_POINTER(aElement); 1.60 + mElement = aElement; 1.61 + mOffset = aOffset; 1.62 + mReplaceLength = aReplaceLength; 1.63 + mStringToInsert = aStringToInsert; 1.64 + mEditor = aEditor; 1.65 + mRanges = aTextRangeArray; 1.66 + mFixed = false; 1.67 + return NS_OK; 1.68 +} 1.69 + 1.70 +NS_IMETHODIMP IMETextTxn::DoTransaction(void) 1.71 +{ 1.72 + 1.73 +#ifdef DEBUG_IMETXN 1.74 + printf("Do IME Text element = %p replace = %d len = %d\n", mElement.get(), mReplaceLength, mStringToInsert.Length()); 1.75 +#endif 1.76 + 1.77 + nsCOMPtr<nsISelectionController> selCon; 1.78 + mEditor->GetSelectionController(getter_AddRefs(selCon)); 1.79 + NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED); 1.80 + 1.81 + // advance caret: This requires the presentation shell to get the selection. 1.82 + nsresult result; 1.83 + if (mReplaceLength == 0) { 1.84 + result = mElement->InsertData(mOffset, mStringToInsert); 1.85 + } else { 1.86 + result = mElement->ReplaceData(mOffset, mReplaceLength, mStringToInsert); 1.87 + } 1.88 + if (NS_SUCCEEDED(result)) { 1.89 + result = SetSelectionForRanges(); 1.90 + } 1.91 + 1.92 + return result; 1.93 +} 1.94 + 1.95 +NS_IMETHODIMP IMETextTxn::UndoTransaction(void) 1.96 +{ 1.97 +#ifdef DEBUG_IMETXN 1.98 + printf("Undo IME Text element = %p\n", mElement.get()); 1.99 +#endif 1.100 + 1.101 + nsCOMPtr<nsISelectionController> selCon; 1.102 + mEditor->GetSelectionController(getter_AddRefs(selCon)); 1.103 + NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED); 1.104 + 1.105 + nsresult result = mElement->DeleteData(mOffset, mStringToInsert.Length()); 1.106 + if (NS_SUCCEEDED(result)) 1.107 + { // set the selection to the insertion point where the string was removed 1.108 + nsCOMPtr<nsISelection> selection; 1.109 + result = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); 1.110 + if (NS_SUCCEEDED(result) && selection) { 1.111 + result = selection->Collapse(mElement, mOffset); 1.112 + NS_ASSERTION((NS_SUCCEEDED(result)), "selection could not be collapsed after undo of IME insert."); 1.113 + } 1.114 + } 1.115 + return result; 1.116 +} 1.117 + 1.118 +NS_IMETHODIMP IMETextTxn::Merge(nsITransaction *aTransaction, bool *aDidMerge) 1.119 +{ 1.120 + NS_ASSERTION(aDidMerge, "illegal vaule- null ptr- aDidMerge"); 1.121 + NS_ASSERTION(aTransaction, "illegal vaule- null ptr- aTransaction"); 1.122 + NS_ENSURE_TRUE(aDidMerge && aTransaction, NS_ERROR_NULL_POINTER); 1.123 + 1.124 +#ifdef DEBUG_IMETXN 1.125 + printf("Merge IME Text element = %p\n", mElement.get()); 1.126 +#endif 1.127 + 1.128 + // 1.129 + // check to make sure we aren't fixed, if we are then nothing get's absorbed 1.130 + // 1.131 + if (mFixed) { 1.132 + *aDidMerge = false; 1.133 + return NS_OK; 1.134 + } 1.135 + 1.136 + // 1.137 + // if aTransaction is another IMETextTxn then absorb it 1.138 + // 1.139 + IMETextTxn* otherTxn = nullptr; 1.140 + nsresult result = aTransaction->QueryInterface(IMETextTxn::GetCID(),(void**)&otherTxn); 1.141 + if (otherTxn && NS_SUCCEEDED(result)) 1.142 + { 1.143 + // 1.144 + // we absorb the next IME transaction by adopting its insert string as our own 1.145 + // 1.146 + mStringToInsert = otherTxn->mStringToInsert; 1.147 + mRanges = otherTxn->mRanges; 1.148 + *aDidMerge = true; 1.149 +#ifdef DEBUG_IMETXN 1.150 + printf("IMETextTxn assimilated IMETextTxn:%p\n", aTransaction); 1.151 +#endif 1.152 + NS_RELEASE(otherTxn); 1.153 + return NS_OK; 1.154 + } 1.155 + 1.156 + *aDidMerge = false; 1.157 + return NS_OK; 1.158 +} 1.159 + 1.160 +NS_IMETHODIMP IMETextTxn::MarkFixed(void) 1.161 +{ 1.162 + mFixed = true; 1.163 + return NS_OK; 1.164 +} 1.165 + 1.166 +NS_IMETHODIMP IMETextTxn::GetTxnDescription(nsAString& aString) 1.167 +{ 1.168 + aString.AssignLiteral("IMETextTxn: "); 1.169 + aString += mStringToInsert; 1.170 + return NS_OK; 1.171 +} 1.172 + 1.173 +/* ============ protected methods ================== */ 1.174 +static SelectionType 1.175 +ToSelectionType(uint32_t aTextRangeType) 1.176 +{ 1.177 + switch(aTextRangeType) { 1.178 + case NS_TEXTRANGE_RAWINPUT: 1.179 + return nsISelectionController::SELECTION_IME_RAWINPUT; 1.180 + case NS_TEXTRANGE_SELECTEDRAWTEXT: 1.181 + return nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT; 1.182 + case NS_TEXTRANGE_CONVERTEDTEXT: 1.183 + return nsISelectionController::SELECTION_IME_CONVERTEDTEXT; 1.184 + case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT: 1.185 + return nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT; 1.186 + default: 1.187 + MOZ_CRASH("Selection type is invalid"); 1.188 + return nsISelectionController::SELECTION_NORMAL; 1.189 + } 1.190 +} 1.191 + 1.192 +nsresult 1.193 +IMETextTxn::SetSelectionForRanges() 1.194 +{ 1.195 + nsCOMPtr<nsISelectionController> selCon; 1.196 + mEditor->GetSelectionController(getter_AddRefs(selCon)); 1.197 + NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED); 1.198 + 1.199 + nsCOMPtr<nsISelection> selection; 1.200 + nsresult rv = 1.201 + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, 1.202 + getter_AddRefs(selection)); 1.203 + NS_ENSURE_SUCCESS(rv, rv); 1.204 + 1.205 + nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection)); 1.206 + rv = selPriv->StartBatchChanges(); 1.207 + NS_ENSURE_SUCCESS(rv, rv); 1.208 + 1.209 + // First, remove all selections of IME composition. 1.210 + static const SelectionType kIMESelections[] = { 1.211 + nsISelectionController::SELECTION_IME_RAWINPUT, 1.212 + nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT, 1.213 + nsISelectionController::SELECTION_IME_CONVERTEDTEXT, 1.214 + nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT 1.215 + }; 1.216 + for (uint32_t i = 0; i < ArrayLength(kIMESelections); ++i) { 1.217 + nsCOMPtr<nsISelection> selectionOfIME; 1.218 + if (NS_FAILED(selCon->GetSelection(kIMESelections[i], 1.219 + getter_AddRefs(selectionOfIME)))) { 1.220 + continue; 1.221 + } 1.222 + DebugOnly<nsresult> rv = selectionOfIME->RemoveAllRanges(); 1.223 + NS_ASSERTION(NS_SUCCEEDED(rv), 1.224 + "Failed to remove all ranges of IME selection"); 1.225 + } 1.226 + 1.227 + // Set caret position and selection of IME composition with TextRangeArray. 1.228 + bool setCaret = false; 1.229 + uint32_t countOfRanges = mRanges ? mRanges->Length() : 0; 1.230 + for (uint32_t i = 0; i < countOfRanges; ++i) { 1.231 + const TextRange& textRange = mRanges->ElementAt(i); 1.232 + 1.233 + // Caret needs special handling since its length may be 0 and if it's not 1.234 + // specified explicitly, we need to handle it ourselves later. 1.235 + if (textRange.mRangeType == NS_TEXTRANGE_CARETPOSITION) { 1.236 + NS_ASSERTION(!setCaret, "The ranges already has caret position"); 1.237 + NS_ASSERTION(!textRange.Length(), "nsEditor doesn't support wide caret"); 1.238 + // NOTE: If the caret position is larger than max length of the editor 1.239 + // content, this may fail. 1.240 + rv = selection->Collapse(mElement, mOffset + textRange.mStartOffset); 1.241 + setCaret = setCaret || NS_SUCCEEDED(rv); 1.242 + NS_ASSERTION(setCaret, "Failed to collapse normal selection"); 1.243 + continue; 1.244 + } 1.245 + 1.246 + // If the clause length is 0, it's should be a bug. 1.247 + if (!textRange.Length()) { 1.248 + NS_WARNING("Any clauses must not be empty"); 1.249 + continue; 1.250 + } 1.251 + 1.252 + nsRefPtr<nsRange> clauseRange; 1.253 + rv = nsRange::CreateRange(mElement, mOffset + textRange.mStartOffset, 1.254 + mElement, mOffset + textRange.mEndOffset, 1.255 + getter_AddRefs(clauseRange)); 1.256 + if (NS_FAILED(rv)) { 1.257 + NS_WARNING("Failed to create a DOM range for a clause of composition"); 1.258 + break; 1.259 + } 1.260 + 1.261 + // Set the range of the clause to selection. 1.262 + nsCOMPtr<nsISelection> selectionOfIME; 1.263 + rv = selCon->GetSelection(ToSelectionType(textRange.mRangeType), 1.264 + getter_AddRefs(selectionOfIME)); 1.265 + if (NS_FAILED(rv)) { 1.266 + NS_WARNING("Failed to get IME selection"); 1.267 + break; 1.268 + } 1.269 + 1.270 + rv = selectionOfIME->AddRange(clauseRange); 1.271 + if (NS_FAILED(rv)) { 1.272 + NS_WARNING("Failed to add selection range for a clause of composition"); 1.273 + break; 1.274 + } 1.275 + 1.276 + // Set the style of the clause. 1.277 + nsCOMPtr<nsISelectionPrivate> selectionOfIMEPriv = 1.278 + do_QueryInterface(selectionOfIME); 1.279 + if (!selectionOfIMEPriv) { 1.280 + NS_WARNING("Failed to get nsISelectionPrivate interface from selection"); 1.281 + continue; // Since this is additional feature, we can continue this job. 1.282 + } 1.283 + rv = selectionOfIMEPriv->SetTextRangeStyle(clauseRange, 1.284 + textRange.mRangeStyle); 1.285 + if (NS_FAILED(rv)) { 1.286 + NS_WARNING("Failed to set selection style"); 1.287 + break; // but this is unexpected... 1.288 + } 1.289 + } 1.290 + 1.291 + // If the ranges doesn't include explicit caret position, let's set the 1.292 + // caret to the end of composition string. 1.293 + if (!setCaret) { 1.294 + rv = selection->Collapse(mElement, mOffset + mStringToInsert.Length()); 1.295 + NS_ASSERTION(NS_SUCCEEDED(rv), 1.296 + "Failed to set caret at the end of composition string"); 1.297 + } 1.298 + 1.299 + rv = selPriv->EndBatchChanges(); 1.300 + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to end batch changes"); 1.301 + 1.302 + return rv; 1.303 +} 1.304 +