Line data Source code
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 "CompositionTransaction.h"
7 :
8 : #include "mozilla/EditorBase.h" // mEditorBase
9 : #include "mozilla/SelectionState.h" // RangeUpdater
10 : #include "mozilla/dom/Selection.h" // local var
11 : #include "mozilla/dom/Text.h" // mTextNode
12 : #include "nsAString.h" // params
13 : #include "nsDebug.h" // for NS_ASSERTION, etc
14 : #include "nsError.h" // for NS_SUCCEEDED, NS_FAILED, etc
15 : #include "nsIPresShell.h" // nsISelectionController constants
16 : #include "nsRange.h" // local var
17 : #include "nsQueryObject.h" // for do_QueryObject
18 :
19 : namespace mozilla {
20 :
21 : using namespace dom;
22 :
23 0 : CompositionTransaction::CompositionTransaction(
24 : Text& aTextNode,
25 : uint32_t aOffset,
26 : uint32_t aReplaceLength,
27 : TextRangeArray* aTextRangeArray,
28 : const nsAString& aStringToInsert,
29 : EditorBase& aEditorBase,
30 0 : RangeUpdater* aRangeUpdater)
31 : : mTextNode(&aTextNode)
32 : , mOffset(aOffset)
33 : , mReplaceLength(aReplaceLength)
34 : , mRanges(aTextRangeArray)
35 : , mStringToInsert(aStringToInsert)
36 : , mEditorBase(&aEditorBase)
37 : , mRangeUpdater(aRangeUpdater)
38 0 : , mFixed(false)
39 : {
40 0 : MOZ_ASSERT(mTextNode->TextLength() >= mOffset);
41 0 : }
42 :
43 0 : CompositionTransaction::~CompositionTransaction()
44 : {
45 0 : }
46 :
47 0 : NS_IMPL_CYCLE_COLLECTION_INHERITED(CompositionTransaction, EditTransactionBase,
48 : mEditorBase,
49 : mTextNode)
50 : // mRangeList can't lead to cycles
51 :
52 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompositionTransaction)
53 0 : if (aIID.Equals(NS_GET_IID(CompositionTransaction))) {
54 0 : foundInterface = static_cast<nsITransaction*>(this);
55 : } else
56 0 : NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
57 :
58 0 : NS_IMPL_ADDREF_INHERITED(CompositionTransaction, EditTransactionBase)
59 0 : NS_IMPL_RELEASE_INHERITED(CompositionTransaction, EditTransactionBase)
60 :
61 : NS_IMETHODIMP
62 0 : CompositionTransaction::DoTransaction()
63 : {
64 0 : if (NS_WARN_IF(!mEditorBase)) {
65 0 : return NS_ERROR_NOT_INITIALIZED;
66 : }
67 :
68 : // Fail before making any changes if there's no selection controller
69 0 : nsCOMPtr<nsISelectionController> selCon;
70 0 : mEditorBase->GetSelectionController(getter_AddRefs(selCon));
71 0 : NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
72 :
73 : // Advance caret: This requires the presentation shell to get the selection.
74 0 : if (mReplaceLength == 0) {
75 0 : nsresult rv = mTextNode->InsertData(mOffset, mStringToInsert);
76 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
77 0 : return rv;
78 : }
79 0 : mRangeUpdater->SelAdjInsertText(*mTextNode, mOffset, mStringToInsert);
80 : } else {
81 0 : uint32_t replaceableLength = mTextNode->TextLength() - mOffset;
82 : nsresult rv =
83 0 : mTextNode->ReplaceData(mOffset, mReplaceLength, mStringToInsert);
84 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
85 0 : return rv;
86 : }
87 0 : mRangeUpdater->SelAdjDeleteText(mTextNode, mOffset, mReplaceLength);
88 0 : mRangeUpdater->SelAdjInsertText(*mTextNode, mOffset, mStringToInsert);
89 :
90 : // If IME text node is multiple node, ReplaceData doesn't remove all IME
91 : // text. So we need remove remained text into other text node.
92 0 : if (replaceableLength < mReplaceLength) {
93 0 : int32_t remainLength = mReplaceLength - replaceableLength;
94 0 : nsCOMPtr<nsINode> node = mTextNode->GetNextSibling();
95 0 : while (node && node->IsNodeOfType(nsINode::eTEXT) &&
96 : remainLength > 0) {
97 0 : Text* text = static_cast<Text*>(node.get());
98 0 : uint32_t textLength = text->TextLength();
99 0 : text->DeleteData(0, remainLength);
100 0 : mRangeUpdater->SelAdjDeleteText(text, 0, remainLength);
101 0 : remainLength -= textLength;
102 0 : node = node->GetNextSibling();
103 : }
104 : }
105 : }
106 :
107 0 : nsresult rv = SetSelectionForRanges();
108 0 : NS_ENSURE_SUCCESS(rv, rv);
109 :
110 0 : return NS_OK;
111 : }
112 :
113 : NS_IMETHODIMP
114 0 : CompositionTransaction::UndoTransaction()
115 : {
116 0 : if (NS_WARN_IF(!mEditorBase)) {
117 0 : return NS_ERROR_NOT_INITIALIZED;
118 : }
119 :
120 : // Get the selection first so we'll fail before making any changes if we
121 : // can't get it
122 0 : RefPtr<Selection> selection = mEditorBase->GetSelection();
123 0 : NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
124 :
125 0 : nsresult rv = mTextNode->DeleteData(mOffset, mStringToInsert.Length());
126 0 : NS_ENSURE_SUCCESS(rv, rv);
127 :
128 : // set the selection to the insertion point where the string was removed
129 0 : rv = selection->Collapse(mTextNode, mOffset);
130 0 : NS_ASSERTION(NS_SUCCEEDED(rv),
131 : "Selection could not be collapsed after undo of IME insert.");
132 0 : NS_ENSURE_SUCCESS(rv, rv);
133 :
134 0 : return NS_OK;
135 : }
136 :
137 : NS_IMETHODIMP
138 0 : CompositionTransaction::Merge(nsITransaction* aTransaction,
139 : bool* aDidMerge)
140 : {
141 0 : NS_ENSURE_ARG_POINTER(aTransaction && aDidMerge);
142 :
143 : // Check to make sure we aren't fixed, if we are then nothing gets absorbed
144 0 : if (mFixed) {
145 0 : *aDidMerge = false;
146 0 : return NS_OK;
147 : }
148 :
149 : // If aTransaction is another CompositionTransaction then absorb it
150 : RefPtr<CompositionTransaction> otherTransaction =
151 0 : do_QueryObject(aTransaction);
152 0 : if (otherTransaction) {
153 : // We absorb the next IME transaction by adopting its insert string
154 0 : mStringToInsert = otherTransaction->mStringToInsert;
155 0 : mRanges = otherTransaction->mRanges;
156 0 : *aDidMerge = true;
157 0 : return NS_OK;
158 : }
159 :
160 0 : *aDidMerge = false;
161 0 : return NS_OK;
162 : }
163 :
164 : void
165 0 : CompositionTransaction::MarkFixed()
166 : {
167 0 : mFixed = true;
168 0 : }
169 :
170 : NS_IMETHODIMP
171 0 : CompositionTransaction::GetTxnDescription(nsAString& aString)
172 : {
173 0 : aString.AssignLiteral("CompositionTransaction: ");
174 0 : aString += mStringToInsert;
175 0 : return NS_OK;
176 : }
177 :
178 : /* ============ private methods ================== */
179 :
180 : nsresult
181 0 : CompositionTransaction::SetSelectionForRanges()
182 : {
183 0 : if (NS_WARN_IF(!mEditorBase)) {
184 0 : return NS_ERROR_NOT_INITIALIZED;
185 : }
186 0 : return SetIMESelection(*mEditorBase, mTextNode, mOffset,
187 0 : mStringToInsert.Length(), mRanges);
188 : }
189 :
190 : // static
191 : nsresult
192 0 : CompositionTransaction::SetIMESelection(EditorBase& aEditorBase,
193 : Text* aTextNode,
194 : uint32_t aOffsetInNode,
195 : uint32_t aLengthOfCompositionString,
196 : const TextRangeArray* aRanges)
197 : {
198 0 : RefPtr<Selection> selection = aEditorBase.GetSelection();
199 0 : NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
200 :
201 0 : nsresult rv = selection->StartBatchChanges();
202 0 : NS_ENSURE_SUCCESS(rv, rv);
203 :
204 : // First, remove all selections of IME composition.
205 : static const RawSelectionType kIMESelections[] = {
206 : nsISelectionController::SELECTION_IME_RAWINPUT,
207 : nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT,
208 : nsISelectionController::SELECTION_IME_CONVERTEDTEXT,
209 : nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
210 : };
211 :
212 0 : nsCOMPtr<nsISelectionController> selCon;
213 0 : aEditorBase.GetSelectionController(getter_AddRefs(selCon));
214 0 : NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
215 :
216 0 : for (uint32_t i = 0; i < ArrayLength(kIMESelections); ++i) {
217 0 : nsCOMPtr<nsISelection> selectionOfIME;
218 0 : if (NS_FAILED(selCon->GetSelection(kIMESelections[i],
219 : getter_AddRefs(selectionOfIME)))) {
220 0 : continue;
221 : }
222 0 : rv = selectionOfIME->RemoveAllRanges();
223 0 : NS_ASSERTION(NS_SUCCEEDED(rv),
224 : "Failed to remove all ranges of IME selection");
225 : }
226 :
227 : // Set caret position and selection of IME composition with TextRangeArray.
228 0 : bool setCaret = false;
229 0 : uint32_t countOfRanges = aRanges ? aRanges->Length() : 0;
230 :
231 : #ifdef DEBUG
232 : // Bounds-checking on debug builds
233 0 : uint32_t maxOffset = aTextNode->Length();
234 : #endif
235 :
236 : // NOTE: composition string may be truncated when it's committed and
237 : // maxlength attribute value doesn't allow input of all text of this
238 : // composition.
239 0 : for (uint32_t i = 0; i < countOfRanges; ++i) {
240 0 : const TextRange& textRange = aRanges->ElementAt(i);
241 :
242 : // Caret needs special handling since its length may be 0 and if it's not
243 : // specified explicitly, we need to handle it ourselves later.
244 0 : if (textRange.mRangeType == TextRangeType::eCaret) {
245 0 : NS_ASSERTION(!setCaret, "The ranges already has caret position");
246 0 : NS_ASSERTION(!textRange.Length(),
247 : "EditorBase doesn't support wide caret");
248 : int32_t caretOffset = static_cast<int32_t>(
249 0 : aOffsetInNode +
250 0 : std::min(textRange.mStartOffset, aLengthOfCompositionString));
251 0 : MOZ_ASSERT(caretOffset >= 0 &&
252 : static_cast<uint32_t>(caretOffset) <= maxOffset);
253 0 : rv = selection->Collapse(aTextNode, caretOffset);
254 0 : setCaret = setCaret || NS_SUCCEEDED(rv);
255 0 : if (NS_WARN_IF(!setCaret)) {
256 0 : continue;
257 : }
258 : // If caret range is specified explicitly, we should show the caret if
259 : // it should be so.
260 0 : aEditorBase.HideCaret(false);
261 0 : continue;
262 : }
263 :
264 : // If the clause length is 0, it should be a bug.
265 0 : if (!textRange.Length()) {
266 0 : NS_WARNING("Any clauses must not be empty");
267 0 : continue;
268 : }
269 :
270 0 : RefPtr<nsRange> clauseRange;
271 : int32_t startOffset = static_cast<int32_t>(
272 0 : aOffsetInNode +
273 0 : std::min(textRange.mStartOffset, aLengthOfCompositionString));
274 0 : MOZ_ASSERT(startOffset >= 0 &&
275 : static_cast<uint32_t>(startOffset) <= maxOffset);
276 : int32_t endOffset = static_cast<int32_t>(
277 0 : aOffsetInNode +
278 0 : std::min(textRange.mEndOffset, aLengthOfCompositionString));
279 0 : MOZ_ASSERT(endOffset >= startOffset &&
280 : static_cast<uint32_t>(endOffset) <= maxOffset);
281 0 : rv = nsRange::CreateRange(aTextNode, startOffset,
282 : aTextNode, endOffset,
283 0 : getter_AddRefs(clauseRange));
284 0 : if (NS_FAILED(rv)) {
285 0 : NS_WARNING("Failed to create a DOM range for a clause of composition");
286 0 : break;
287 : }
288 :
289 : // Set the range of the clause to selection.
290 0 : nsCOMPtr<nsISelection> selectionOfIME;
291 0 : rv = selCon->GetSelection(ToRawSelectionType(textRange.mRangeType),
292 0 : getter_AddRefs(selectionOfIME));
293 0 : if (NS_FAILED(rv)) {
294 0 : NS_WARNING("Failed to get IME selection");
295 0 : break;
296 : }
297 :
298 0 : rv = selectionOfIME->AddRange(clauseRange);
299 0 : if (NS_FAILED(rv)) {
300 0 : NS_WARNING("Failed to add selection range for a clause of composition");
301 0 : break;
302 : }
303 :
304 : // Set the style of the clause.
305 : nsCOMPtr<nsISelectionPrivate> selectionOfIMEPriv =
306 0 : do_QueryInterface(selectionOfIME);
307 0 : if (!selectionOfIMEPriv) {
308 0 : NS_WARNING("Failed to get nsISelectionPrivate interface from selection");
309 0 : continue; // Since this is additional feature, we can continue this job.
310 : }
311 0 : rv = selectionOfIMEPriv->SetTextRangeStyle(clauseRange,
312 0 : textRange.mRangeStyle);
313 0 : if (NS_FAILED(rv)) {
314 0 : NS_WARNING("Failed to set selection style");
315 0 : break; // but this is unexpected...
316 : }
317 : }
318 :
319 : // If the ranges doesn't include explicit caret position, let's set the
320 : // caret to the end of composition string.
321 0 : if (!setCaret) {
322 : int32_t caretOffset =
323 0 : static_cast<int32_t>(aOffsetInNode + aLengthOfCompositionString);
324 0 : MOZ_ASSERT(caretOffset >= 0 &&
325 : static_cast<uint32_t>(caretOffset) <= maxOffset);
326 0 : rv = selection->Collapse(aTextNode, caretOffset);
327 0 : NS_ASSERTION(NS_SUCCEEDED(rv),
328 : "Failed to set caret at the end of composition string");
329 :
330 : // If caret range isn't specified explicitly, we should hide the caret.
331 : // Hiding the caret benefits a Windows build (see bug 555642 comment #6).
332 : // However, when there is no range, we should keep showing caret.
333 0 : if (countOfRanges) {
334 0 : aEditorBase.HideCaret(true);
335 : }
336 : }
337 :
338 0 : rv = selection->EndBatchChangesInternal();
339 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to end batch changes");
340 :
341 0 : return rv;
342 : }
343 :
344 : } // namespace mozilla
|