|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "IMETextTxn.h" |
|
7 #include "mozilla/DebugOnly.h" // for DebugOnly |
|
8 #include "mozilla/mozalloc.h" // for operator new |
|
9 #include "mozilla/TextEvents.h" // for TextRangeStyle |
|
10 #include "nsAString.h" // for nsAString_internal::Length, etc |
|
11 #include "nsAutoPtr.h" // for nsRefPtr |
|
12 #include "nsDebug.h" // for NS_ASSERTION, etc |
|
13 #include "nsError.h" // for NS_SUCCEEDED, NS_FAILED, etc |
|
14 #include "nsIDOMCharacterData.h" // for nsIDOMCharacterData |
|
15 #include "nsIDOMRange.h" // for nsRange::SetEnd, etc |
|
16 #include "nsIContent.h" // for nsIContent |
|
17 #include "nsIEditor.h" // for nsIEditor |
|
18 #include "nsIPresShell.h" // for SelectionType |
|
19 #include "nsISelection.h" // for nsISelection |
|
20 #include "nsISelectionController.h" // for nsISelectionController, etc |
|
21 #include "nsISelectionPrivate.h" // for nsISelectionPrivate |
|
22 #include "nsISupportsImpl.h" // for nsRange::AddRef, etc |
|
23 #include "nsISupportsUtils.h" // for NS_ADDREF_THIS, NS_RELEASE |
|
24 #include "nsITransaction.h" // for nsITransaction |
|
25 #include "nsRange.h" // for nsRange |
|
26 #include "nsString.h" // for nsString |
|
27 |
|
28 using namespace mozilla; |
|
29 |
|
30 // #define DEBUG_IMETXN |
|
31 |
|
32 IMETextTxn::IMETextTxn() |
|
33 : EditTxn() |
|
34 { |
|
35 } |
|
36 |
|
37 NS_IMPL_CYCLE_COLLECTION_INHERITED(IMETextTxn, EditTxn, |
|
38 mElement) |
|
39 // mRangeList can't lead to cycles |
|
40 |
|
41 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMETextTxn) |
|
42 if (aIID.Equals(IMETextTxn::GetCID())) { |
|
43 *aInstancePtr = (void*)(IMETextTxn*)this; |
|
44 NS_ADDREF_THIS(); |
|
45 return NS_OK; |
|
46 } else |
|
47 NS_INTERFACE_MAP_END_INHERITING(EditTxn) |
|
48 |
|
49 NS_IMETHODIMP IMETextTxn::Init(nsIDOMCharacterData *aElement, |
|
50 uint32_t aOffset, |
|
51 uint32_t aReplaceLength, |
|
52 TextRangeArray *aTextRangeArray, |
|
53 const nsAString &aStringToInsert, |
|
54 nsIEditor *aEditor) |
|
55 { |
|
56 NS_ENSURE_ARG_POINTER(aElement); |
|
57 mElement = aElement; |
|
58 mOffset = aOffset; |
|
59 mReplaceLength = aReplaceLength; |
|
60 mStringToInsert = aStringToInsert; |
|
61 mEditor = aEditor; |
|
62 mRanges = aTextRangeArray; |
|
63 mFixed = false; |
|
64 return NS_OK; |
|
65 } |
|
66 |
|
67 NS_IMETHODIMP IMETextTxn::DoTransaction(void) |
|
68 { |
|
69 |
|
70 #ifdef DEBUG_IMETXN |
|
71 printf("Do IME Text element = %p replace = %d len = %d\n", mElement.get(), mReplaceLength, mStringToInsert.Length()); |
|
72 #endif |
|
73 |
|
74 nsCOMPtr<nsISelectionController> selCon; |
|
75 mEditor->GetSelectionController(getter_AddRefs(selCon)); |
|
76 NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED); |
|
77 |
|
78 // advance caret: This requires the presentation shell to get the selection. |
|
79 nsresult result; |
|
80 if (mReplaceLength == 0) { |
|
81 result = mElement->InsertData(mOffset, mStringToInsert); |
|
82 } else { |
|
83 result = mElement->ReplaceData(mOffset, mReplaceLength, mStringToInsert); |
|
84 } |
|
85 if (NS_SUCCEEDED(result)) { |
|
86 result = SetSelectionForRanges(); |
|
87 } |
|
88 |
|
89 return result; |
|
90 } |
|
91 |
|
92 NS_IMETHODIMP IMETextTxn::UndoTransaction(void) |
|
93 { |
|
94 #ifdef DEBUG_IMETXN |
|
95 printf("Undo IME Text element = %p\n", mElement.get()); |
|
96 #endif |
|
97 |
|
98 nsCOMPtr<nsISelectionController> selCon; |
|
99 mEditor->GetSelectionController(getter_AddRefs(selCon)); |
|
100 NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED); |
|
101 |
|
102 nsresult result = mElement->DeleteData(mOffset, mStringToInsert.Length()); |
|
103 if (NS_SUCCEEDED(result)) |
|
104 { // set the selection to the insertion point where the string was removed |
|
105 nsCOMPtr<nsISelection> selection; |
|
106 result = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); |
|
107 if (NS_SUCCEEDED(result) && selection) { |
|
108 result = selection->Collapse(mElement, mOffset); |
|
109 NS_ASSERTION((NS_SUCCEEDED(result)), "selection could not be collapsed after undo of IME insert."); |
|
110 } |
|
111 } |
|
112 return result; |
|
113 } |
|
114 |
|
115 NS_IMETHODIMP IMETextTxn::Merge(nsITransaction *aTransaction, bool *aDidMerge) |
|
116 { |
|
117 NS_ASSERTION(aDidMerge, "illegal vaule- null ptr- aDidMerge"); |
|
118 NS_ASSERTION(aTransaction, "illegal vaule- null ptr- aTransaction"); |
|
119 NS_ENSURE_TRUE(aDidMerge && aTransaction, NS_ERROR_NULL_POINTER); |
|
120 |
|
121 #ifdef DEBUG_IMETXN |
|
122 printf("Merge IME Text element = %p\n", mElement.get()); |
|
123 #endif |
|
124 |
|
125 // |
|
126 // check to make sure we aren't fixed, if we are then nothing get's absorbed |
|
127 // |
|
128 if (mFixed) { |
|
129 *aDidMerge = false; |
|
130 return NS_OK; |
|
131 } |
|
132 |
|
133 // |
|
134 // if aTransaction is another IMETextTxn then absorb it |
|
135 // |
|
136 IMETextTxn* otherTxn = nullptr; |
|
137 nsresult result = aTransaction->QueryInterface(IMETextTxn::GetCID(),(void**)&otherTxn); |
|
138 if (otherTxn && NS_SUCCEEDED(result)) |
|
139 { |
|
140 // |
|
141 // we absorb the next IME transaction by adopting its insert string as our own |
|
142 // |
|
143 mStringToInsert = otherTxn->mStringToInsert; |
|
144 mRanges = otherTxn->mRanges; |
|
145 *aDidMerge = true; |
|
146 #ifdef DEBUG_IMETXN |
|
147 printf("IMETextTxn assimilated IMETextTxn:%p\n", aTransaction); |
|
148 #endif |
|
149 NS_RELEASE(otherTxn); |
|
150 return NS_OK; |
|
151 } |
|
152 |
|
153 *aDidMerge = false; |
|
154 return NS_OK; |
|
155 } |
|
156 |
|
157 NS_IMETHODIMP IMETextTxn::MarkFixed(void) |
|
158 { |
|
159 mFixed = true; |
|
160 return NS_OK; |
|
161 } |
|
162 |
|
163 NS_IMETHODIMP IMETextTxn::GetTxnDescription(nsAString& aString) |
|
164 { |
|
165 aString.AssignLiteral("IMETextTxn: "); |
|
166 aString += mStringToInsert; |
|
167 return NS_OK; |
|
168 } |
|
169 |
|
170 /* ============ protected methods ================== */ |
|
171 static SelectionType |
|
172 ToSelectionType(uint32_t aTextRangeType) |
|
173 { |
|
174 switch(aTextRangeType) { |
|
175 case NS_TEXTRANGE_RAWINPUT: |
|
176 return nsISelectionController::SELECTION_IME_RAWINPUT; |
|
177 case NS_TEXTRANGE_SELECTEDRAWTEXT: |
|
178 return nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT; |
|
179 case NS_TEXTRANGE_CONVERTEDTEXT: |
|
180 return nsISelectionController::SELECTION_IME_CONVERTEDTEXT; |
|
181 case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT: |
|
182 return nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT; |
|
183 default: |
|
184 MOZ_CRASH("Selection type is invalid"); |
|
185 return nsISelectionController::SELECTION_NORMAL; |
|
186 } |
|
187 } |
|
188 |
|
189 nsresult |
|
190 IMETextTxn::SetSelectionForRanges() |
|
191 { |
|
192 nsCOMPtr<nsISelectionController> selCon; |
|
193 mEditor->GetSelectionController(getter_AddRefs(selCon)); |
|
194 NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED); |
|
195 |
|
196 nsCOMPtr<nsISelection> selection; |
|
197 nsresult rv = |
|
198 selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, |
|
199 getter_AddRefs(selection)); |
|
200 NS_ENSURE_SUCCESS(rv, rv); |
|
201 |
|
202 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection)); |
|
203 rv = selPriv->StartBatchChanges(); |
|
204 NS_ENSURE_SUCCESS(rv, rv); |
|
205 |
|
206 // First, remove all selections of IME composition. |
|
207 static const SelectionType kIMESelections[] = { |
|
208 nsISelectionController::SELECTION_IME_RAWINPUT, |
|
209 nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT, |
|
210 nsISelectionController::SELECTION_IME_CONVERTEDTEXT, |
|
211 nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT |
|
212 }; |
|
213 for (uint32_t i = 0; i < ArrayLength(kIMESelections); ++i) { |
|
214 nsCOMPtr<nsISelection> selectionOfIME; |
|
215 if (NS_FAILED(selCon->GetSelection(kIMESelections[i], |
|
216 getter_AddRefs(selectionOfIME)))) { |
|
217 continue; |
|
218 } |
|
219 DebugOnly<nsresult> rv = selectionOfIME->RemoveAllRanges(); |
|
220 NS_ASSERTION(NS_SUCCEEDED(rv), |
|
221 "Failed to remove all ranges of IME selection"); |
|
222 } |
|
223 |
|
224 // Set caret position and selection of IME composition with TextRangeArray. |
|
225 bool setCaret = false; |
|
226 uint32_t countOfRanges = mRanges ? mRanges->Length() : 0; |
|
227 for (uint32_t i = 0; i < countOfRanges; ++i) { |
|
228 const TextRange& textRange = mRanges->ElementAt(i); |
|
229 |
|
230 // Caret needs special handling since its length may be 0 and if it's not |
|
231 // specified explicitly, we need to handle it ourselves later. |
|
232 if (textRange.mRangeType == NS_TEXTRANGE_CARETPOSITION) { |
|
233 NS_ASSERTION(!setCaret, "The ranges already has caret position"); |
|
234 NS_ASSERTION(!textRange.Length(), "nsEditor doesn't support wide caret"); |
|
235 // NOTE: If the caret position is larger than max length of the editor |
|
236 // content, this may fail. |
|
237 rv = selection->Collapse(mElement, mOffset + textRange.mStartOffset); |
|
238 setCaret = setCaret || NS_SUCCEEDED(rv); |
|
239 NS_ASSERTION(setCaret, "Failed to collapse normal selection"); |
|
240 continue; |
|
241 } |
|
242 |
|
243 // If the clause length is 0, it's should be a bug. |
|
244 if (!textRange.Length()) { |
|
245 NS_WARNING("Any clauses must not be empty"); |
|
246 continue; |
|
247 } |
|
248 |
|
249 nsRefPtr<nsRange> clauseRange; |
|
250 rv = nsRange::CreateRange(mElement, mOffset + textRange.mStartOffset, |
|
251 mElement, mOffset + textRange.mEndOffset, |
|
252 getter_AddRefs(clauseRange)); |
|
253 if (NS_FAILED(rv)) { |
|
254 NS_WARNING("Failed to create a DOM range for a clause of composition"); |
|
255 break; |
|
256 } |
|
257 |
|
258 // Set the range of the clause to selection. |
|
259 nsCOMPtr<nsISelection> selectionOfIME; |
|
260 rv = selCon->GetSelection(ToSelectionType(textRange.mRangeType), |
|
261 getter_AddRefs(selectionOfIME)); |
|
262 if (NS_FAILED(rv)) { |
|
263 NS_WARNING("Failed to get IME selection"); |
|
264 break; |
|
265 } |
|
266 |
|
267 rv = selectionOfIME->AddRange(clauseRange); |
|
268 if (NS_FAILED(rv)) { |
|
269 NS_WARNING("Failed to add selection range for a clause of composition"); |
|
270 break; |
|
271 } |
|
272 |
|
273 // Set the style of the clause. |
|
274 nsCOMPtr<nsISelectionPrivate> selectionOfIMEPriv = |
|
275 do_QueryInterface(selectionOfIME); |
|
276 if (!selectionOfIMEPriv) { |
|
277 NS_WARNING("Failed to get nsISelectionPrivate interface from selection"); |
|
278 continue; // Since this is additional feature, we can continue this job. |
|
279 } |
|
280 rv = selectionOfIMEPriv->SetTextRangeStyle(clauseRange, |
|
281 textRange.mRangeStyle); |
|
282 if (NS_FAILED(rv)) { |
|
283 NS_WARNING("Failed to set selection style"); |
|
284 break; // but this is unexpected... |
|
285 } |
|
286 } |
|
287 |
|
288 // If the ranges doesn't include explicit caret position, let's set the |
|
289 // caret to the end of composition string. |
|
290 if (!setCaret) { |
|
291 rv = selection->Collapse(mElement, mOffset + mStringToInsert.Length()); |
|
292 NS_ASSERTION(NS_SUCCEEDED(rv), |
|
293 "Failed to set caret at the end of composition string"); |
|
294 } |
|
295 |
|
296 rv = selPriv->EndBatchChanges(); |
|
297 NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to end batch changes"); |
|
298 |
|
299 return rv; |
|
300 } |
|
301 |