Bumping manifests a=b2g-bump
[gecko.git] / editor / libeditor / IMETextTxn.cpp
blob973573aec2db0ef8fe4b758cf983e0a3ab430026
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/. */
6 #include "IMETextTxn.h"
8 #include "mozilla/dom/Selection.h" // local var
9 #include "mozilla/dom/Text.h" // mTextNode
10 #include "nsAString.h" // params
11 #include "nsDebug.h" // for NS_ASSERTION, etc
12 #include "nsEditor.h" // mEditor
13 #include "nsError.h" // for NS_SUCCEEDED, NS_FAILED, etc
14 #include "nsIPresShell.h" // nsISelectionController constants
15 #include "nsRange.h" // local var
17 using namespace mozilla;
18 using namespace mozilla::dom;
20 IMETextTxn::IMETextTxn(Text& aTextNode, uint32_t aOffset,
21 uint32_t aReplaceLength,
22 TextRangeArray* aTextRangeArray,
23 const nsAString& aStringToInsert,
24 nsEditor& aEditor)
25 : EditTxn()
26 , mTextNode(&aTextNode)
27 , mOffset(aOffset)
28 , mReplaceLength(aReplaceLength)
29 , mRanges(aTextRangeArray)
30 , mStringToInsert(aStringToInsert)
31 , mEditor(aEditor)
32 , mFixed(false)
36 IMETextTxn::~IMETextTxn()
40 NS_IMPL_CYCLE_COLLECTION_INHERITED(IMETextTxn, EditTxn,
41 mTextNode)
42 // mRangeList can't lead to cycles
44 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMETextTxn)
45 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsITransaction, IMETextTxn)
46 NS_INTERFACE_MAP_END_INHERITING(EditTxn)
48 NS_IMPL_ADDREF_INHERITED(IMETextTxn, EditTxn)
49 NS_IMPL_RELEASE_INHERITED(IMETextTxn, EditTxn)
51 NS_IMETHODIMP
52 IMETextTxn::DoTransaction()
54 // Fail before making any changes if there's no selection controller
55 nsCOMPtr<nsISelectionController> selCon;
56 mEditor.GetSelectionController(getter_AddRefs(selCon));
57 NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
59 // Advance caret: This requires the presentation shell to get the selection.
60 nsresult res;
61 if (mReplaceLength == 0) {
62 res = mTextNode->InsertData(mOffset, mStringToInsert);
63 } else {
64 res = mTextNode->ReplaceData(mOffset, mReplaceLength, mStringToInsert);
66 NS_ENSURE_SUCCESS(res, res);
68 res = SetSelectionForRanges();
69 NS_ENSURE_SUCCESS(res, res);
71 return NS_OK;
74 NS_IMETHODIMP
75 IMETextTxn::UndoTransaction()
77 // Get the selection first so we'll fail before making any changes if we
78 // can't get it
79 nsRefPtr<Selection> selection = mEditor.GetSelection();
80 NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
82 nsresult res = mTextNode->DeleteData(mOffset, mStringToInsert.Length());
83 NS_ENSURE_SUCCESS(res, res);
85 // set the selection to the insertion point where the string was removed
86 res = selection->Collapse(mTextNode, mOffset);
87 NS_ASSERTION(NS_SUCCEEDED(res),
88 "Selection could not be collapsed after undo of IME insert.");
89 NS_ENSURE_SUCCESS(res, res);
91 return NS_OK;
94 NS_IMETHODIMP
95 IMETextTxn::Merge(nsITransaction* aTransaction, bool* aDidMerge)
97 NS_ENSURE_ARG_POINTER(aTransaction && aDidMerge);
99 // Check to make sure we aren't fixed, if we are then nothing gets absorbed
100 if (mFixed) {
101 *aDidMerge = false;
102 return NS_OK;
105 // If aTransaction is another IMETextTxn then absorb it
106 nsRefPtr<IMETextTxn> otherTxn = do_QueryObject(aTransaction);
107 if (otherTxn) {
108 // We absorb the next IME transaction by adopting its insert string
109 mStringToInsert = otherTxn->mStringToInsert;
110 mRanges = otherTxn->mRanges;
111 *aDidMerge = true;
112 return NS_OK;
115 *aDidMerge = false;
116 return NS_OK;
119 void
120 IMETextTxn::MarkFixed()
122 mFixed = true;
125 NS_IMETHODIMP
126 IMETextTxn::GetTxnDescription(nsAString& aString)
128 aString.AssignLiteral("IMETextTxn: ");
129 aString += mStringToInsert;
130 return NS_OK;
133 /* ============ private methods ================== */
134 static SelectionType
135 ToSelectionType(uint32_t aTextRangeType)
137 switch(aTextRangeType) {
138 case NS_TEXTRANGE_RAWINPUT:
139 return nsISelectionController::SELECTION_IME_RAWINPUT;
140 case NS_TEXTRANGE_SELECTEDRAWTEXT:
141 return nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT;
142 case NS_TEXTRANGE_CONVERTEDTEXT:
143 return nsISelectionController::SELECTION_IME_CONVERTEDTEXT;
144 case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT:
145 return nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT;
146 default:
147 MOZ_CRASH("Selection type is invalid");
148 return nsISelectionController::SELECTION_NORMAL;
152 nsresult
153 IMETextTxn::SetSelectionForRanges()
155 nsRefPtr<Selection> selection = mEditor.GetSelection();
156 NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
158 nsresult rv = selection->StartBatchChanges();
159 NS_ENSURE_SUCCESS(rv, rv);
161 // First, remove all selections of IME composition.
162 static const SelectionType kIMESelections[] = {
163 nsISelectionController::SELECTION_IME_RAWINPUT,
164 nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT,
165 nsISelectionController::SELECTION_IME_CONVERTEDTEXT,
166 nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
169 nsCOMPtr<nsISelectionController> selCon;
170 mEditor.GetSelectionController(getter_AddRefs(selCon));
171 NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
173 for (uint32_t i = 0; i < ArrayLength(kIMESelections); ++i) {
174 nsCOMPtr<nsISelection> selectionOfIME;
175 if (NS_FAILED(selCon->GetSelection(kIMESelections[i],
176 getter_AddRefs(selectionOfIME)))) {
177 continue;
179 rv = selectionOfIME->RemoveAllRanges();
180 NS_ASSERTION(NS_SUCCEEDED(rv),
181 "Failed to remove all ranges of IME selection");
184 // Set caret position and selection of IME composition with TextRangeArray.
185 bool setCaret = false;
186 uint32_t countOfRanges = mRanges ? mRanges->Length() : 0;
188 #ifdef DEBUG
189 // Bounds-checking on debug builds
190 uint32_t maxOffset = mTextNode->Length();
191 #endif
193 // The mStringToInsert may be truncated if maxlength attribute value doesn't
194 // allow input of all text of this composition. So, we can get actual length
195 // of the inserted string from it.
196 uint32_t insertedLength = mStringToInsert.Length();
197 for (uint32_t i = 0; i < countOfRanges; ++i) {
198 const TextRange& textRange = mRanges->ElementAt(i);
200 // Caret needs special handling since its length may be 0 and if it's not
201 // specified explicitly, we need to handle it ourselves later.
202 if (textRange.mRangeType == NS_TEXTRANGE_CARETPOSITION) {
203 NS_ASSERTION(!setCaret, "The ranges already has caret position");
204 NS_ASSERTION(!textRange.Length(), "nsEditor doesn't support wide caret");
205 int32_t caretOffset = static_cast<int32_t>(
206 mOffset + std::min(textRange.mStartOffset, insertedLength));
207 MOZ_ASSERT(caretOffset >= 0 &&
208 static_cast<uint32_t>(caretOffset) <= maxOffset);
209 rv = selection->Collapse(mTextNode, caretOffset);
210 setCaret = setCaret || NS_SUCCEEDED(rv);
211 NS_ASSERTION(setCaret, "Failed to collapse normal selection");
212 continue;
215 // If the clause length is 0, it should be a bug.
216 if (!textRange.Length()) {
217 NS_WARNING("Any clauses must not be empty");
218 continue;
221 nsRefPtr<nsRange> clauseRange;
222 int32_t startOffset = static_cast<int32_t>(
223 mOffset + std::min(textRange.mStartOffset, insertedLength));
224 MOZ_ASSERT(startOffset >= 0 &&
225 static_cast<uint32_t>(startOffset) <= maxOffset);
226 int32_t endOffset = static_cast<int32_t>(
227 mOffset + std::min(textRange.mEndOffset, insertedLength));
228 MOZ_ASSERT(endOffset >= startOffset &&
229 static_cast<uint32_t>(endOffset) <= maxOffset);
230 rv = nsRange::CreateRange(mTextNode, startOffset,
231 mTextNode, endOffset,
232 getter_AddRefs(clauseRange));
233 if (NS_FAILED(rv)) {
234 NS_WARNING("Failed to create a DOM range for a clause of composition");
235 break;
238 // Set the range of the clause to selection.
239 nsCOMPtr<nsISelection> selectionOfIME;
240 rv = selCon->GetSelection(ToSelectionType(textRange.mRangeType),
241 getter_AddRefs(selectionOfIME));
242 if (NS_FAILED(rv)) {
243 NS_WARNING("Failed to get IME selection");
244 break;
247 rv = selectionOfIME->AddRange(clauseRange);
248 if (NS_FAILED(rv)) {
249 NS_WARNING("Failed to add selection range for a clause of composition");
250 break;
253 // Set the style of the clause.
254 nsCOMPtr<nsISelectionPrivate> selectionOfIMEPriv =
255 do_QueryInterface(selectionOfIME);
256 if (!selectionOfIMEPriv) {
257 NS_WARNING("Failed to get nsISelectionPrivate interface from selection");
258 continue; // Since this is additional feature, we can continue this job.
260 rv = selectionOfIMEPriv->SetTextRangeStyle(clauseRange,
261 textRange.mRangeStyle);
262 if (NS_FAILED(rv)) {
263 NS_WARNING("Failed to set selection style");
264 break; // but this is unexpected...
268 // If the ranges doesn't include explicit caret position, let's set the
269 // caret to the end of composition string.
270 if (!setCaret) {
271 int32_t caretOffset = static_cast<int32_t>(mOffset + insertedLength);
272 MOZ_ASSERT(caretOffset >= 0 &&
273 static_cast<uint32_t>(caretOffset) <= maxOffset);
274 rv = selection->Collapse(mTextNode, caretOffset);
275 NS_ASSERTION(NS_SUCCEEDED(rv),
276 "Failed to set caret at the end of composition string");
279 rv = selection->EndBatchChanges();
280 NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to end batch changes");
282 return rv;