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
,
26 , mTextNode(&aTextNode
)
28 , mReplaceLength(aReplaceLength
)
29 , mRanges(aTextRangeArray
)
30 , mStringToInsert(aStringToInsert
)
36 IMETextTxn::~IMETextTxn()
40 NS_IMPL_CYCLE_COLLECTION_INHERITED(IMETextTxn
, EditTxn
,
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
)
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.
61 if (mReplaceLength
== 0) {
62 res
= mTextNode
->InsertData(mOffset
, mStringToInsert
);
64 res
= mTextNode
->ReplaceData(mOffset
, mReplaceLength
, mStringToInsert
);
66 NS_ENSURE_SUCCESS(res
, res
);
68 res
= SetSelectionForRanges();
69 NS_ENSURE_SUCCESS(res
, res
);
75 IMETextTxn::UndoTransaction()
77 // Get the selection first so we'll fail before making any changes if we
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
);
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
105 // If aTransaction is another IMETextTxn then absorb it
106 nsRefPtr
<IMETextTxn
> otherTxn
= do_QueryObject(aTransaction
);
108 // We absorb the next IME transaction by adopting its insert string
109 mStringToInsert
= otherTxn
->mStringToInsert
;
110 mRanges
= otherTxn
->mRanges
;
120 IMETextTxn::MarkFixed()
126 IMETextTxn::GetTxnDescription(nsAString
& aString
)
128 aString
.AssignLiteral("IMETextTxn: ");
129 aString
+= mStringToInsert
;
133 /* ============ private methods ================== */
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
;
147 MOZ_CRASH("Selection type is invalid");
148 return nsISelectionController::SELECTION_NORMAL
;
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
)))) {
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;
189 // Bounds-checking on debug builds
190 uint32_t maxOffset
= mTextNode
->Length();
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");
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");
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
));
234 NS_WARNING("Failed to create a DOM range for a clause of composition");
238 // Set the range of the clause to selection.
239 nsCOMPtr
<nsISelection
> selectionOfIME
;
240 rv
= selCon
->GetSelection(ToSelectionType(textRange
.mRangeType
),
241 getter_AddRefs(selectionOfIME
));
243 NS_WARNING("Failed to get IME selection");
247 rv
= selectionOfIME
->AddRange(clauseRange
);
249 NS_WARNING("Failed to add selection range for a clause of composition");
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
);
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.
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");