xref: /haiku/src/apps/haikudepot/textview/TextDocument.cpp (revision 79a174c793158690dca8954d3deac3dd97376dc4)
1d7f7bf2dSAxel Dörfler /*
2d7f7bf2dSAxel Dörfler  * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
33d2fd2acSAndrew Lindesay  * Copyright 2021, Andrew Lindesay <apl@lindesay.co.nz>.
4d7f7bf2dSAxel Dörfler  * All rights reserved. Distributed under the terms of the MIT License.
5d7f7bf2dSAxel Dörfler  */
6d7f7bf2dSAxel Dörfler 
7d7f7bf2dSAxel Dörfler #include "TextDocument.h"
8d7f7bf2dSAxel Dörfler 
9d7f7bf2dSAxel Dörfler #include <algorithm>
10d7f7bf2dSAxel Dörfler #include <stdio.h>
113d2fd2acSAndrew Lindesay #include <vector>
12d7f7bf2dSAxel Dörfler 
13d7f7bf2dSAxel Dörfler 
14d7f7bf2dSAxel Dörfler TextDocument::TextDocument()
15d7f7bf2dSAxel Dörfler 	:
16d7f7bf2dSAxel Dörfler 	fParagraphs(),
17d7f7bf2dSAxel Dörfler 	fEmptyLastParagraph(),
18d7f7bf2dSAxel Dörfler 	fDefaultCharacterStyle()
19d7f7bf2dSAxel Dörfler {
20d7f7bf2dSAxel Dörfler }
21d7f7bf2dSAxel Dörfler 
22d7f7bf2dSAxel Dörfler 
23f890fab6SStephan Aßmus TextDocument::TextDocument(CharacterStyle characterStyle,
24f890fab6SStephan Aßmus 	ParagraphStyle paragraphStyle)
25d7f7bf2dSAxel Dörfler 	:
26d7f7bf2dSAxel Dörfler 	fParagraphs(),
27d7f7bf2dSAxel Dörfler 	fEmptyLastParagraph(paragraphStyle),
28d7f7bf2dSAxel Dörfler 	fDefaultCharacterStyle(characterStyle)
29d7f7bf2dSAxel Dörfler {
30d7f7bf2dSAxel Dörfler }
31d7f7bf2dSAxel Dörfler 
32d7f7bf2dSAxel Dörfler 
33d7f7bf2dSAxel Dörfler TextDocument::TextDocument(const TextDocument& other)
34d7f7bf2dSAxel Dörfler 	:
35d7f7bf2dSAxel Dörfler 	fParagraphs(other.fParagraphs),
36d7f7bf2dSAxel Dörfler 	fEmptyLastParagraph(other.fEmptyLastParagraph),
37d7f7bf2dSAxel Dörfler 	fDefaultCharacterStyle(other.fDefaultCharacterStyle)
38d7f7bf2dSAxel Dörfler {
39d7f7bf2dSAxel Dörfler }
40d7f7bf2dSAxel Dörfler 
41d7f7bf2dSAxel Dörfler 
42d7f7bf2dSAxel Dörfler TextDocument&
43d7f7bf2dSAxel Dörfler TextDocument::operator=(const TextDocument& other)
44d7f7bf2dSAxel Dörfler {
45d7f7bf2dSAxel Dörfler 	fParagraphs = other.fParagraphs;
46d7f7bf2dSAxel Dörfler 	fEmptyLastParagraph = other.fEmptyLastParagraph;
47d7f7bf2dSAxel Dörfler 	fDefaultCharacterStyle = other.fDefaultCharacterStyle;
48d7f7bf2dSAxel Dörfler 
49d7f7bf2dSAxel Dörfler 	return *this;
50d7f7bf2dSAxel Dörfler }
51d7f7bf2dSAxel Dörfler 
52d7f7bf2dSAxel Dörfler 
53d7f7bf2dSAxel Dörfler bool
54d7f7bf2dSAxel Dörfler TextDocument::operator==(const TextDocument& other) const
55d7f7bf2dSAxel Dörfler {
56d7f7bf2dSAxel Dörfler 	if (this == &other)
57d7f7bf2dSAxel Dörfler 		return true;
58d7f7bf2dSAxel Dörfler 
59d7f7bf2dSAxel Dörfler 	return fEmptyLastParagraph == other.fEmptyLastParagraph
60d7f7bf2dSAxel Dörfler 		&& fDefaultCharacterStyle == other.fDefaultCharacterStyle
61d7f7bf2dSAxel Dörfler 		&& fParagraphs == other.fParagraphs;
62d7f7bf2dSAxel Dörfler }
63d7f7bf2dSAxel Dörfler 
64d7f7bf2dSAxel Dörfler 
65d7f7bf2dSAxel Dörfler bool
66d7f7bf2dSAxel Dörfler TextDocument::operator!=(const TextDocument& other) const
67d7f7bf2dSAxel Dörfler {
68d7f7bf2dSAxel Dörfler 	return !(*this == other);
69d7f7bf2dSAxel Dörfler }
70d7f7bf2dSAxel Dörfler 
71d7f7bf2dSAxel Dörfler 
72d7f7bf2dSAxel Dörfler // #pragma mark -
73d7f7bf2dSAxel Dörfler 
74d7f7bf2dSAxel Dörfler 
75d7f7bf2dSAxel Dörfler status_t
76d7f7bf2dSAxel Dörfler TextDocument::Insert(int32 textOffset, const BString& text)
77d7f7bf2dSAxel Dörfler {
784a96bcdaSStephan Aßmus 	return Replace(textOffset, 0, text);
79d7f7bf2dSAxel Dörfler }
80d7f7bf2dSAxel Dörfler 
81d7f7bf2dSAxel Dörfler 
82d7f7bf2dSAxel Dörfler status_t
83d7f7bf2dSAxel Dörfler TextDocument::Insert(int32 textOffset, const BString& text,
84f890fab6SStephan Aßmus 	CharacterStyle style)
85d7f7bf2dSAxel Dörfler {
864a96bcdaSStephan Aßmus 	return Replace(textOffset, 0, text, style);
87d7f7bf2dSAxel Dörfler }
88d7f7bf2dSAxel Dörfler 
89d7f7bf2dSAxel Dörfler 
90d7f7bf2dSAxel Dörfler status_t
91d7f7bf2dSAxel Dörfler TextDocument::Insert(int32 textOffset, const BString& text,
92f890fab6SStephan Aßmus 	CharacterStyle characterStyle, ParagraphStyle paragraphStyle)
93d7f7bf2dSAxel Dörfler {
944a96bcdaSStephan Aßmus 	return Replace(textOffset, 0, text, characterStyle, paragraphStyle);
95d7f7bf2dSAxel Dörfler }
96d7f7bf2dSAxel Dörfler 
97d7f7bf2dSAxel Dörfler 
98d7f7bf2dSAxel Dörfler // #pragma mark -
99d7f7bf2dSAxel Dörfler 
100d7f7bf2dSAxel Dörfler 
101d7f7bf2dSAxel Dörfler status_t
102d7f7bf2dSAxel Dörfler TextDocument::Remove(int32 textOffset, int32 length)
103d7f7bf2dSAxel Dörfler {
1044a96bcdaSStephan Aßmus 	return Replace(textOffset, length, BString());
105d7f7bf2dSAxel Dörfler }
106d7f7bf2dSAxel Dörfler 
107d7f7bf2dSAxel Dörfler 
108d7f7bf2dSAxel Dörfler // #pragma mark -
109d7f7bf2dSAxel Dörfler 
110d7f7bf2dSAxel Dörfler 
111d7f7bf2dSAxel Dörfler status_t
112d7f7bf2dSAxel Dörfler TextDocument::Replace(int32 textOffset, int32 length, const BString& text)
113d7f7bf2dSAxel Dörfler {
114d7f7bf2dSAxel Dörfler 	return Replace(textOffset, length, text, CharacterStyleAt(textOffset));
115d7f7bf2dSAxel Dörfler }
116d7f7bf2dSAxel Dörfler 
117d7f7bf2dSAxel Dörfler 
118d7f7bf2dSAxel Dörfler status_t
119d7f7bf2dSAxel Dörfler TextDocument::Replace(int32 textOffset, int32 length, const BString& text,
120f890fab6SStephan Aßmus 	CharacterStyle style)
121d7f7bf2dSAxel Dörfler {
122d7f7bf2dSAxel Dörfler 	return Replace(textOffset, length, text, style,
123d7f7bf2dSAxel Dörfler 		ParagraphStyleAt(textOffset));
124d7f7bf2dSAxel Dörfler }
125d7f7bf2dSAxel Dörfler 
126d7f7bf2dSAxel Dörfler 
127d7f7bf2dSAxel Dörfler status_t
128d7f7bf2dSAxel Dörfler TextDocument::Replace(int32 textOffset, int32 length, const BString& text,
129f890fab6SStephan Aßmus 	CharacterStyle characterStyle, ParagraphStyle paragraphStyle)
130f890fab6SStephan Aßmus {
131f890fab6SStephan Aßmus 	TextDocumentRef document = NormalizeText(text, characterStyle,
132f890fab6SStephan Aßmus 		paragraphStyle);
133779ab335SX512 	if (!document.IsSet() || document->Length() != text.CountChars())
134f890fab6SStephan Aßmus 		return B_NO_MEMORY;
135f890fab6SStephan Aßmus 	return Replace(textOffset, length, document);
136f890fab6SStephan Aßmus }
137f890fab6SStephan Aßmus 
138f890fab6SStephan Aßmus 
139f890fab6SStephan Aßmus status_t
140f890fab6SStephan Aßmus TextDocument::Replace(int32 textOffset, int32 length, TextDocumentRef document)
141d7f7bf2dSAxel Dörfler {
1424a96bcdaSStephan Aßmus 	int32 firstParagraph = 0;
1434a96bcdaSStephan Aßmus 	int32 paragraphCount = 0;
1444a96bcdaSStephan Aßmus 
1454a96bcdaSStephan Aßmus 	// TODO: Call _NotifyTextChanging() before any change happened
1464a96bcdaSStephan Aßmus 
1474a96bcdaSStephan Aßmus 	status_t ret = _Remove(textOffset, length, firstParagraph, paragraphCount);
148d7f7bf2dSAxel Dörfler 	if (ret != B_OK)
149d7f7bf2dSAxel Dörfler 		return ret;
150d7f7bf2dSAxel Dörfler 
151f890fab6SStephan Aßmus 	ret = _Insert(textOffset, document, firstParagraph, paragraphCount);
1524a96bcdaSStephan Aßmus 
1534a96bcdaSStephan Aßmus 	_NotifyTextChanged(TextChangedEvent(firstParagraph, paragraphCount));
1544a96bcdaSStephan Aßmus 
1554a96bcdaSStephan Aßmus 	return ret;
156d7f7bf2dSAxel Dörfler }
157d7f7bf2dSAxel Dörfler 
158d7f7bf2dSAxel Dörfler 
159d7f7bf2dSAxel Dörfler // #pragma mark -
160d7f7bf2dSAxel Dörfler 
161d7f7bf2dSAxel Dörfler 
162d7f7bf2dSAxel Dörfler const CharacterStyle&
163d7f7bf2dSAxel Dörfler TextDocument::CharacterStyleAt(int32 textOffset) const
164d7f7bf2dSAxel Dörfler {
165d7f7bf2dSAxel Dörfler 	int32 paragraphOffset;
166d7f7bf2dSAxel Dörfler 	const Paragraph& paragraph = ParagraphAt(textOffset, paragraphOffset);
167d7f7bf2dSAxel Dörfler 
168d7f7bf2dSAxel Dörfler 	textOffset -= paragraphOffset;
1693d2fd2acSAndrew Lindesay 	int32 index;
1703d2fd2acSAndrew Lindesay 	int32 count = paragraph.CountTextSpans();
171d7f7bf2dSAxel Dörfler 
1723d2fd2acSAndrew Lindesay 	for (index = 0; index < count; index++) {
1733d2fd2acSAndrew Lindesay 		const TextSpan& span = paragraph.TextSpanAtIndex(index);
174d7f7bf2dSAxel Dörfler 		if (textOffset - span.CountChars() < 0)
175d7f7bf2dSAxel Dörfler 			return span.Style();
176d7f7bf2dSAxel Dörfler 		textOffset -= span.CountChars();
177d7f7bf2dSAxel Dörfler 	}
178d7f7bf2dSAxel Dörfler 
179d7f7bf2dSAxel Dörfler 	return fDefaultCharacterStyle;
180d7f7bf2dSAxel Dörfler }
181d7f7bf2dSAxel Dörfler 
182d7f7bf2dSAxel Dörfler 
183d7f7bf2dSAxel Dörfler const ParagraphStyle&
184d7f7bf2dSAxel Dörfler TextDocument::ParagraphStyleAt(int32 textOffset) const
185d7f7bf2dSAxel Dörfler {
186d7f7bf2dSAxel Dörfler 	int32 paragraphOffset;
187d7f7bf2dSAxel Dörfler 	return ParagraphAt(textOffset, paragraphOffset).Style();
188d7f7bf2dSAxel Dörfler }
189d7f7bf2dSAxel Dörfler 
190d7f7bf2dSAxel Dörfler 
191d7f7bf2dSAxel Dörfler // #pragma mark -
192d7f7bf2dSAxel Dörfler 
193d7f7bf2dSAxel Dörfler 
194d7f7bf2dSAxel Dörfler int32
195f890fab6SStephan Aßmus TextDocument::CountParagraphs() const
196f890fab6SStephan Aßmus {
197dfbcbde1SAndrew Lindesay 	return fParagraphs.size();
198dfbcbde1SAndrew Lindesay }
199dfbcbde1SAndrew Lindesay 
200dfbcbde1SAndrew Lindesay 
201dfbcbde1SAndrew Lindesay const Paragraph&
202dfbcbde1SAndrew Lindesay TextDocument::ParagraphAtIndex(int32 index) const
203dfbcbde1SAndrew Lindesay {
204dfbcbde1SAndrew Lindesay 	return fParagraphs[index];
205f890fab6SStephan Aßmus }
206f890fab6SStephan Aßmus 
207f890fab6SStephan Aßmus 
208f890fab6SStephan Aßmus int32
209d7f7bf2dSAxel Dörfler TextDocument::ParagraphIndexFor(int32 textOffset, int32& paragraphOffset) const
210d7f7bf2dSAxel Dörfler {
211d7f7bf2dSAxel Dörfler 	// TODO: Could binary search the Paragraphs if they were wrapped in classes
212d7f7bf2dSAxel Dörfler 	// that knew there text offset in the document.
213d7f7bf2dSAxel Dörfler 	int32 textLength = 0;
214d7f7bf2dSAxel Dörfler 	paragraphOffset = 0;
215dfbcbde1SAndrew Lindesay 	int32 count = fParagraphs.size();
216d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
217dfbcbde1SAndrew Lindesay 		const Paragraph& paragraph = fParagraphs[i];
218d7f7bf2dSAxel Dörfler 		int32 paragraphLength = paragraph.Length();
219d7f7bf2dSAxel Dörfler 		textLength += paragraphLength;
220d7f7bf2dSAxel Dörfler 		if (textLength > textOffset
221d7f7bf2dSAxel Dörfler 			|| (i == count - 1 && textLength == textOffset)) {
222d7f7bf2dSAxel Dörfler 			return i;
223d7f7bf2dSAxel Dörfler 		}
224d7f7bf2dSAxel Dörfler 		paragraphOffset += paragraphLength;
225d7f7bf2dSAxel Dörfler 	}
226d7f7bf2dSAxel Dörfler 	return -1;
227d7f7bf2dSAxel Dörfler }
228d7f7bf2dSAxel Dörfler 
229d7f7bf2dSAxel Dörfler 
230d7f7bf2dSAxel Dörfler const Paragraph&
231d7f7bf2dSAxel Dörfler TextDocument::ParagraphAt(int32 textOffset, int32& paragraphOffset) const
232d7f7bf2dSAxel Dörfler {
233d7f7bf2dSAxel Dörfler 	int32 index = ParagraphIndexFor(textOffset, paragraphOffset);
234d7f7bf2dSAxel Dörfler 	if (index >= 0)
235dfbcbde1SAndrew Lindesay 		return fParagraphs[index];
236d7f7bf2dSAxel Dörfler 
237d7f7bf2dSAxel Dörfler 	return fEmptyLastParagraph;
238d7f7bf2dSAxel Dörfler }
239d7f7bf2dSAxel Dörfler 
240d7f7bf2dSAxel Dörfler 
241d7f7bf2dSAxel Dörfler const Paragraph&
242d7f7bf2dSAxel Dörfler TextDocument::ParagraphAt(int32 index) const
243d7f7bf2dSAxel Dörfler {
244dfbcbde1SAndrew Lindesay 	if (index >= 0 && index < static_cast<int32>(fParagraphs.size()))
245dfbcbde1SAndrew Lindesay 		return fParagraphs[index];
246d7f7bf2dSAxel Dörfler 	return fEmptyLastParagraph;
247d7f7bf2dSAxel Dörfler }
248d7f7bf2dSAxel Dörfler 
249d7f7bf2dSAxel Dörfler 
250d7f7bf2dSAxel Dörfler bool
251d7f7bf2dSAxel Dörfler TextDocument::Append(const Paragraph& paragraph)
252d7f7bf2dSAxel Dörfler {
253dfbcbde1SAndrew Lindesay 	try {
254dfbcbde1SAndrew Lindesay 		fParagraphs.push_back(paragraph);
255dfbcbde1SAndrew Lindesay 	}
256dfbcbde1SAndrew Lindesay 	catch (std::bad_alloc& ba) {
257dfbcbde1SAndrew Lindesay 		fprintf(stderr, "bad_alloc when adding a paragraph to a text "
258dfbcbde1SAndrew Lindesay 			"document\n");
259dfbcbde1SAndrew Lindesay 		return false;
260dfbcbde1SAndrew Lindesay 	}
261dfbcbde1SAndrew Lindesay 	return true;
262d7f7bf2dSAxel Dörfler }
263d7f7bf2dSAxel Dörfler 
264d7f7bf2dSAxel Dörfler 
265d7f7bf2dSAxel Dörfler int32
266d7f7bf2dSAxel Dörfler TextDocument::Length() const
267d7f7bf2dSAxel Dörfler {
268d7f7bf2dSAxel Dörfler 	// TODO: Could be O(1) if the Paragraphs were wrapped in classes that
2694a96bcdaSStephan Aßmus 	// knew their text offset in the document.
270d7f7bf2dSAxel Dörfler 	int32 textLength = 0;
271dfbcbde1SAndrew Lindesay 	int32 count = fParagraphs.size();
272d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
273dfbcbde1SAndrew Lindesay 		const Paragraph& paragraph = fParagraphs[i];
274d7f7bf2dSAxel Dörfler 		textLength += paragraph.Length();
275d7f7bf2dSAxel Dörfler 	}
276d7f7bf2dSAxel Dörfler 	return textLength;
277d7f7bf2dSAxel Dörfler }
278d7f7bf2dSAxel Dörfler 
279d7f7bf2dSAxel Dörfler 
280d7f7bf2dSAxel Dörfler BString
281d7f7bf2dSAxel Dörfler TextDocument::Text() const
282d7f7bf2dSAxel Dörfler {
283d7f7bf2dSAxel Dörfler 	return Text(0, Length());
284d7f7bf2dSAxel Dörfler }
285d7f7bf2dSAxel Dörfler 
286d7f7bf2dSAxel Dörfler 
287d7f7bf2dSAxel Dörfler BString
288d7f7bf2dSAxel Dörfler TextDocument::Text(int32 start, int32 length) const
289d7f7bf2dSAxel Dörfler {
290d7f7bf2dSAxel Dörfler 	if (start < 0)
291d7f7bf2dSAxel Dörfler 		start = 0;
292d7f7bf2dSAxel Dörfler 
293d7f7bf2dSAxel Dörfler 	BString text;
294d7f7bf2dSAxel Dörfler 
295dfbcbde1SAndrew Lindesay 	int32 count = fParagraphs.size();
296d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
297dfbcbde1SAndrew Lindesay 		const Paragraph& paragraph = fParagraphs[i];
298d7f7bf2dSAxel Dörfler 		int32 paragraphLength = paragraph.Length();
299d7f7bf2dSAxel Dörfler 		if (paragraphLength == 0)
300d7f7bf2dSAxel Dörfler 			continue;
301d7f7bf2dSAxel Dörfler 		if (start > paragraphLength) {
302d7f7bf2dSAxel Dörfler 			// Skip paragraph if its before start
303d7f7bf2dSAxel Dörfler 			start -= paragraphLength;
304d7f7bf2dSAxel Dörfler 			continue;
305d7f7bf2dSAxel Dörfler 		}
306d7f7bf2dSAxel Dörfler 
307d7f7bf2dSAxel Dörfler 		// Remaining paragraph length after start
308d7f7bf2dSAxel Dörfler 		paragraphLength -= start;
309d7f7bf2dSAxel Dörfler 		int32 copyLength = std::min(paragraphLength, length);
310d7f7bf2dSAxel Dörfler 
311d7f7bf2dSAxel Dörfler 		text << paragraph.Text(start, copyLength);
312d7f7bf2dSAxel Dörfler 
313d7f7bf2dSAxel Dörfler 		length -= copyLength;
314d7f7bf2dSAxel Dörfler 		if (length == 0)
315d7f7bf2dSAxel Dörfler 			break;
316d7f7bf2dSAxel Dörfler 
317d7f7bf2dSAxel Dörfler 		// Next paragraph is copied from its beginning
318d7f7bf2dSAxel Dörfler 		start = 0;
319d7f7bf2dSAxel Dörfler 	}
320d7f7bf2dSAxel Dörfler 
321d7f7bf2dSAxel Dörfler 	return text;
322d7f7bf2dSAxel Dörfler }
323d7f7bf2dSAxel Dörfler 
324d7f7bf2dSAxel Dörfler 
325d7f7bf2dSAxel Dörfler TextDocumentRef
326d7f7bf2dSAxel Dörfler TextDocument::SubDocument(int32 start, int32 length) const
327d7f7bf2dSAxel Dörfler {
328d7f7bf2dSAxel Dörfler 	TextDocumentRef result(new(std::nothrow) TextDocument(
329d7f7bf2dSAxel Dörfler 		fDefaultCharacterStyle, fEmptyLastParagraph.Style()), true);
330d7f7bf2dSAxel Dörfler 
331779ab335SX512 	if (!result.IsSet())
332d7f7bf2dSAxel Dörfler 		return result;
333d7f7bf2dSAxel Dörfler 
334d7f7bf2dSAxel Dörfler 	if (start < 0)
335d7f7bf2dSAxel Dörfler 		start = 0;
336d7f7bf2dSAxel Dörfler 
337dfbcbde1SAndrew Lindesay 	int32 count = fParagraphs.size();
338d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
339dfbcbde1SAndrew Lindesay 		const Paragraph& paragraph = fParagraphs[i];
340d7f7bf2dSAxel Dörfler 		int32 paragraphLength = paragraph.Length();
341d7f7bf2dSAxel Dörfler 		if (paragraphLength == 0)
342d7f7bf2dSAxel Dörfler 			continue;
343d7f7bf2dSAxel Dörfler 		if (start > paragraphLength) {
344d7f7bf2dSAxel Dörfler 			// Skip paragraph if its before start
345d7f7bf2dSAxel Dörfler 			start -= paragraphLength;
346d7f7bf2dSAxel Dörfler 			continue;
347d7f7bf2dSAxel Dörfler 		}
348d7f7bf2dSAxel Dörfler 
349d7f7bf2dSAxel Dörfler 		// Remaining paragraph length after start
350d7f7bf2dSAxel Dörfler 		paragraphLength -= start;
351d7f7bf2dSAxel Dörfler 		int32 copyLength = std::min(paragraphLength, length);
352d7f7bf2dSAxel Dörfler 
353d7f7bf2dSAxel Dörfler 		result->Append(paragraph.SubParagraph(start, copyLength));
354d7f7bf2dSAxel Dörfler 
355d7f7bf2dSAxel Dörfler 		length -= copyLength;
356d7f7bf2dSAxel Dörfler 		if (length == 0)
357d7f7bf2dSAxel Dörfler 			break;
358d7f7bf2dSAxel Dörfler 
359d7f7bf2dSAxel Dörfler 		// Next paragraph is copied from its beginning
360d7f7bf2dSAxel Dörfler 		start = 0;
361d7f7bf2dSAxel Dörfler 	}
362d7f7bf2dSAxel Dörfler 
363d7f7bf2dSAxel Dörfler 	return result;
364d7f7bf2dSAxel Dörfler }
365d7f7bf2dSAxel Dörfler 
366d7f7bf2dSAxel Dörfler 
367d7f7bf2dSAxel Dörfler // #pragma mark -
368d7f7bf2dSAxel Dörfler 
369d7f7bf2dSAxel Dörfler 
370d7f7bf2dSAxel Dörfler void
371d7f7bf2dSAxel Dörfler TextDocument::PrintToStream() const
372d7f7bf2dSAxel Dörfler {
373dfbcbde1SAndrew Lindesay 	int32 paragraphCount = fParagraphs.size();
374d7f7bf2dSAxel Dörfler 	if (paragraphCount == 0) {
375d7f7bf2dSAxel Dörfler 		printf("<document/>\n");
376d7f7bf2dSAxel Dörfler 		return;
377d7f7bf2dSAxel Dörfler 	}
378d7f7bf2dSAxel Dörfler 	printf("<document>\n");
379d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < paragraphCount; i++) {
380dfbcbde1SAndrew Lindesay 		fParagraphs[i].PrintToStream();
381d7f7bf2dSAxel Dörfler 	}
382d7f7bf2dSAxel Dörfler 	printf("</document>\n");
383d7f7bf2dSAxel Dörfler }
384d7f7bf2dSAxel Dörfler 
385d7f7bf2dSAxel Dörfler 
386f890fab6SStephan Aßmus /*static*/ TextDocumentRef
387f890fab6SStephan Aßmus TextDocument::NormalizeText(const BString& text,
388f890fab6SStephan Aßmus 	CharacterStyle characterStyle, ParagraphStyle paragraphStyle)
389d7f7bf2dSAxel Dörfler {
390f890fab6SStephan Aßmus 	TextDocumentRef document(new(std::nothrow) TextDocument(characterStyle,
391f890fab6SStephan Aßmus 			paragraphStyle), true);
392779ab335SX512 	if (!document.IsSet())
393f890fab6SStephan Aßmus 		throw B_NO_MEMORY;
394d7f7bf2dSAxel Dörfler 
395f890fab6SStephan Aßmus 	Paragraph paragraph(paragraphStyle);
396d7f7bf2dSAxel Dörfler 
397f890fab6SStephan Aßmus 	// Append TextSpans, splitting 'text' into Paragraphs at line breaks.
3984a96bcdaSStephan Aßmus 	int32 length = text.CountChars();
3994a96bcdaSStephan Aßmus 	int32 chunkStart = 0;
4004a96bcdaSStephan Aßmus 	while (chunkStart < length) {
4014a96bcdaSStephan Aßmus 		int32 chunkEnd = text.FindFirst('\n', chunkStart);
4024a96bcdaSStephan Aßmus 		bool foundLineBreak = chunkEnd >= chunkStart;
4034a96bcdaSStephan Aßmus 		if (foundLineBreak)
4044a96bcdaSStephan Aßmus 			chunkEnd++;
4054a96bcdaSStephan Aßmus 		else
4064a96bcdaSStephan Aßmus 			chunkEnd = length;
4074a96bcdaSStephan Aßmus 
4084a96bcdaSStephan Aßmus 		BString chunk;
4094a96bcdaSStephan Aßmus 		text.CopyCharsInto(chunk, chunkStart, chunkEnd - chunkStart);
4104a96bcdaSStephan Aßmus 		TextSpan span(chunk, characterStyle);
4114a96bcdaSStephan Aßmus 
412f890fab6SStephan Aßmus 		if (!paragraph.Append(span))
413f890fab6SStephan Aßmus 			throw B_NO_MEMORY;
414f890fab6SStephan Aßmus 		if (paragraph.Length() > 0 && !document->Append(paragraph))
415f890fab6SStephan Aßmus 			throw B_NO_MEMORY;
4164a96bcdaSStephan Aßmus 
417f890fab6SStephan Aßmus 		paragraph = Paragraph(paragraphStyle);
4184a96bcdaSStephan Aßmus 		chunkStart = chunkEnd + 1;
4194a96bcdaSStephan Aßmus 	}
4204a96bcdaSStephan Aßmus 
421f890fab6SStephan Aßmus 	return document;
422f890fab6SStephan Aßmus }
423f890fab6SStephan Aßmus 
424f890fab6SStephan Aßmus 
425f890fab6SStephan Aßmus // #pragma mark -
426f890fab6SStephan Aßmus 
427f890fab6SStephan Aßmus 
428f890fab6SStephan Aßmus bool
429f890fab6SStephan Aßmus TextDocument::AddListener(TextListenerRef listener)
430f890fab6SStephan Aßmus {
431dfbcbde1SAndrew Lindesay 	try {
432dfbcbde1SAndrew Lindesay 		fTextListeners.push_back(listener);
433dfbcbde1SAndrew Lindesay 	}
434dfbcbde1SAndrew Lindesay 	catch (std::bad_alloc& ba) {
435dfbcbde1SAndrew Lindesay 		fprintf(stderr, "bad_alloc when adding a listener to a text "
436dfbcbde1SAndrew Lindesay 			"document\n");
437dfbcbde1SAndrew Lindesay 		return false;
438dfbcbde1SAndrew Lindesay 	}
439dfbcbde1SAndrew Lindesay 	return true;
440f890fab6SStephan Aßmus }
441f890fab6SStephan Aßmus 
442f890fab6SStephan Aßmus 
443f890fab6SStephan Aßmus bool
444f890fab6SStephan Aßmus TextDocument::RemoveListener(TextListenerRef listener)
445f890fab6SStephan Aßmus {
446*79a174c7SAugustin Cavalier 	fTextListeners.erase(std::remove(fTextListeners.begin(), fTextListeners.end(),
447*79a174c7SAugustin Cavalier 		listener), fTextListeners.end());
448dfbcbde1SAndrew Lindesay 	return true;
449f890fab6SStephan Aßmus }
450f890fab6SStephan Aßmus 
451f890fab6SStephan Aßmus 
452f890fab6SStephan Aßmus bool
453f890fab6SStephan Aßmus TextDocument::AddUndoListener(UndoableEditListenerRef listener)
454f890fab6SStephan Aßmus {
455dfbcbde1SAndrew Lindesay 	try {
456dfbcbde1SAndrew Lindesay 		fUndoListeners.push_back(listener);
457dfbcbde1SAndrew Lindesay 	}
458dfbcbde1SAndrew Lindesay 	catch (std::bad_alloc& ba) {
459dfbcbde1SAndrew Lindesay 		fprintf(stderr, "bad_alloc when adding an undo listener to a text "
460dfbcbde1SAndrew Lindesay 			"document\n");
461dfbcbde1SAndrew Lindesay 		return false;
462dfbcbde1SAndrew Lindesay 	}
463dfbcbde1SAndrew Lindesay 	return true;
464f890fab6SStephan Aßmus }
465f890fab6SStephan Aßmus 
466f890fab6SStephan Aßmus 
467f890fab6SStephan Aßmus bool
468f890fab6SStephan Aßmus TextDocument::RemoveUndoListener(UndoableEditListenerRef listener)
469f890fab6SStephan Aßmus {
470dfbcbde1SAndrew Lindesay 	std::remove(fUndoListeners.begin(), fUndoListeners.end(), listener);
471dfbcbde1SAndrew Lindesay 	return true;
472f890fab6SStephan Aßmus }
473f890fab6SStephan Aßmus 
474f890fab6SStephan Aßmus 
475f890fab6SStephan Aßmus // #pragma mark - private
476f890fab6SStephan Aßmus 
477f890fab6SStephan Aßmus 
478f890fab6SStephan Aßmus status_t
479f890fab6SStephan Aßmus TextDocument::_Insert(int32 textOffset, TextDocumentRef document,
480f890fab6SStephan Aßmus 	int32& index, int32& paragraphCount)
481f890fab6SStephan Aßmus {
482f890fab6SStephan Aßmus 	int32 paragraphOffset;
483f890fab6SStephan Aßmus 	index = ParagraphIndexFor(textOffset, paragraphOffset);
484f890fab6SStephan Aßmus 	if (index < 0)
485f890fab6SStephan Aßmus 		return B_BAD_VALUE;
486f890fab6SStephan Aßmus 
487f890fab6SStephan Aßmus 	if (document->Length() == 0)
488f890fab6SStephan Aßmus 		return B_OK;
489f890fab6SStephan Aßmus 
490f890fab6SStephan Aßmus 	textOffset -= paragraphOffset;
491f890fab6SStephan Aßmus 
492f890fab6SStephan Aßmus 	bool hasLineBreaks;
493f890fab6SStephan Aßmus 	if (document->CountParagraphs() > 1) {
494f890fab6SStephan Aßmus 		hasLineBreaks = true;
495f890fab6SStephan Aßmus 	} else {
496f890fab6SStephan Aßmus 		const Paragraph& paragraph = document->ParagraphAt(0);
497f890fab6SStephan Aßmus 		hasLineBreaks = paragraph.EndsWith("\n");
498f890fab6SStephan Aßmus 	}
499f890fab6SStephan Aßmus 
500f890fab6SStephan Aßmus 	if (hasLineBreaks) {
501f890fab6SStephan Aßmus 		// Split paragraph at textOffset
502f890fab6SStephan Aßmus 		Paragraph paragraph1(ParagraphAt(index).Style());
503f890fab6SStephan Aßmus 		Paragraph paragraph2(document->ParagraphAt(
504f890fab6SStephan Aßmus 			document->CountParagraphs() - 1).Style());
505f890fab6SStephan Aßmus 		{
5063d2fd2acSAndrew Lindesay 			const Paragraph& paragraphAtIndex = ParagraphAt(index);
5073d2fd2acSAndrew Lindesay 			int32 spanCount = paragraphAtIndex.CountTextSpans();
508f890fab6SStephan Aßmus 			for (int32 i = 0; i < spanCount; i++) {
5093d2fd2acSAndrew Lindesay 				const TextSpan& span = paragraphAtIndex.TextSpanAtIndex(i);
510f890fab6SStephan Aßmus 				int32 spanLength = span.CountChars();
511f890fab6SStephan Aßmus 				if (textOffset >= spanLength) {
512f890fab6SStephan Aßmus 					if (!paragraph1.Append(span))
513f890fab6SStephan Aßmus 						return B_NO_MEMORY;
514f890fab6SStephan Aßmus 					textOffset -= spanLength;
515f890fab6SStephan Aßmus 				} else if (textOffset > 0) {
516f890fab6SStephan Aßmus 					if (!paragraph1.Append(
517f890fab6SStephan Aßmus 							span.SubSpan(0, textOffset))
518f890fab6SStephan Aßmus 						|| !paragraph2.Append(
519f890fab6SStephan Aßmus 							span.SubSpan(textOffset,
520f890fab6SStephan Aßmus 								spanLength - textOffset))) {
521f890fab6SStephan Aßmus 						return B_NO_MEMORY;
522f890fab6SStephan Aßmus 					}
523f890fab6SStephan Aßmus 					textOffset = 0;
524f890fab6SStephan Aßmus 				} else {
525f890fab6SStephan Aßmus 					if (!paragraph2.Append(span))
526f890fab6SStephan Aßmus 						return B_NO_MEMORY;
527f890fab6SStephan Aßmus 				}
528f890fab6SStephan Aßmus 			}
529f890fab6SStephan Aßmus 		}
530f890fab6SStephan Aßmus 
531dfbcbde1SAndrew Lindesay 		fParagraphs.erase(fParagraphs.begin() + index);
532f890fab6SStephan Aßmus 
533f890fab6SStephan Aßmus 		// Append first paragraph in other document to first part of
534f890fab6SStephan Aßmus 		// paragraph at insert position
535f890fab6SStephan Aßmus 		{
536f890fab6SStephan Aßmus 			const Paragraph& otherParagraph = document->ParagraphAt(0);
5373d2fd2acSAndrew Lindesay 			int32 spanCount = otherParagraph.CountTextSpans();
538f890fab6SStephan Aßmus 			for (int32 i = 0; i < spanCount; i++) {
5393d2fd2acSAndrew Lindesay 				const TextSpan& span = otherParagraph.TextSpanAtIndex(i);
540f890fab6SStephan Aßmus 				// TODO: Import/map CharacterStyles
541f890fab6SStephan Aßmus 				if (!paragraph1.Append(span))
542f890fab6SStephan Aßmus 					return B_NO_MEMORY;
543f890fab6SStephan Aßmus 			}
544f890fab6SStephan Aßmus 		}
545f890fab6SStephan Aßmus 
546f890fab6SStephan Aßmus 		// Insert the first paragraph-part again to the document
547dfbcbde1SAndrew Lindesay 		try {
548dfbcbde1SAndrew Lindesay 			fParagraphs.insert(fParagraphs.begin() + index, paragraph1);
549dfbcbde1SAndrew Lindesay 		}
550dfbcbde1SAndrew Lindesay 		catch (std::bad_alloc& ba) {
551f890fab6SStephan Aßmus 			return B_NO_MEMORY;
552dfbcbde1SAndrew Lindesay 		}
553f890fab6SStephan Aßmus 		paragraphCount++;
554f890fab6SStephan Aßmus 
555f890fab6SStephan Aßmus 		// Insert the other document's paragraph save for the last one
556f890fab6SStephan Aßmus 		for (int32 i = 1; i < document->CountParagraphs() - 1; i++) {
557f890fab6SStephan Aßmus 			const Paragraph& otherParagraph = document->ParagraphAt(i);
558f890fab6SStephan Aßmus 			// TODO: Import/map CharacterStyles and ParagraphStyle
559dfbcbde1SAndrew Lindesay 			index++;
560dfbcbde1SAndrew Lindesay 			try {
561dfbcbde1SAndrew Lindesay 				fParagraphs.insert(fParagraphs.begin() + index, otherParagraph);
562dfbcbde1SAndrew Lindesay 			}
563dfbcbde1SAndrew Lindesay 			catch (std::bad_alloc& ba) {
564f890fab6SStephan Aßmus 				return B_NO_MEMORY;
565dfbcbde1SAndrew Lindesay 			}
566f890fab6SStephan Aßmus 			paragraphCount++;
567f890fab6SStephan Aßmus 		}
568f890fab6SStephan Aßmus 
569f890fab6SStephan Aßmus 		int32 lastIndex = document->CountParagraphs() - 1;
570f890fab6SStephan Aßmus 		if (lastIndex > 0) {
571f890fab6SStephan Aßmus 			const Paragraph& otherParagraph = document->ParagraphAt(lastIndex);
572f890fab6SStephan Aßmus 			if (otherParagraph.EndsWith("\n")) {
573f890fab6SStephan Aßmus 				// TODO: Import/map CharacterStyles and ParagraphStyle
574dfbcbde1SAndrew Lindesay 				index++;
575dfbcbde1SAndrew Lindesay 				try {
576dfbcbde1SAndrew Lindesay 					fParagraphs.insert(fParagraphs.begin() + index, otherParagraph);
577dfbcbde1SAndrew Lindesay 				}
578dfbcbde1SAndrew Lindesay 				catch (std::bad_alloc& ba) {
579f890fab6SStephan Aßmus 					return B_NO_MEMORY;
580dfbcbde1SAndrew Lindesay 				}
581f890fab6SStephan Aßmus 			} else {
5823d2fd2acSAndrew Lindesay 				int32 spanCount = otherParagraph.CountTextSpans();
583f890fab6SStephan Aßmus 				for (int32 i = 0; i < spanCount; i++) {
5843d2fd2acSAndrew Lindesay 					const TextSpan& span = otherParagraph.TextSpanAtIndex(i);
585f890fab6SStephan Aßmus 					// TODO: Import/map CharacterStyles
586f890fab6SStephan Aßmus 					if (!paragraph2.Prepend(span))
587f890fab6SStephan Aßmus 						return B_NO_MEMORY;
588f890fab6SStephan Aßmus 				}
589f890fab6SStephan Aßmus 			}
590f890fab6SStephan Aßmus 		}
591f890fab6SStephan Aßmus 
592f890fab6SStephan Aßmus 		// Insert back the second paragraph-part
5934a96bcdaSStephan Aßmus 		if (paragraph2.IsEmpty()) {
5944a96bcdaSStephan Aßmus 			// Make sure Paragraph has at least one TextSpan, even
5955f80d48aSStephan Aßmus 			// if its empty. This handles the case of inserting a
5965f80d48aSStephan Aßmus 			// line-break at the end of the document. It than needs to
5975f80d48aSStephan Aßmus 			// have a new, empty paragraph at the end.
5983d2fd2acSAndrew Lindesay 			const int32 indexLastSpan = paragraph1.CountTextSpans() - 1;
5993d2fd2acSAndrew Lindesay 			const TextSpan& span = paragraph1.TextSpanAtIndex(indexLastSpan);
600f890fab6SStephan Aßmus 			if (!paragraph2.Append(TextSpan("", span.Style())))
601f890fab6SStephan Aßmus 				return B_NO_MEMORY;
6024a96bcdaSStephan Aßmus 		}
6034a96bcdaSStephan Aßmus 
604dfbcbde1SAndrew Lindesay 		index++;
605dfbcbde1SAndrew Lindesay 		try {
606dfbcbde1SAndrew Lindesay 			fParagraphs.insert(fParagraphs.begin() + index, paragraph2);
607dfbcbde1SAndrew Lindesay 		}
608dfbcbde1SAndrew Lindesay 		catch (std::bad_alloc& ba) {
6094a96bcdaSStephan Aßmus 			return B_NO_MEMORY;
610dfbcbde1SAndrew Lindesay 		}
611f890fab6SStephan Aßmus 
6124a96bcdaSStephan Aßmus 		paragraphCount++;
6134a96bcdaSStephan Aßmus 	} else {
6144a96bcdaSStephan Aßmus 		Paragraph paragraph(ParagraphAt(index));
615f890fab6SStephan Aßmus 		const Paragraph& otherParagraph = document->ParagraphAt(0);
616f890fab6SStephan Aßmus 
6173d2fd2acSAndrew Lindesay 		int32 spanCount = otherParagraph.CountTextSpans();
618f890fab6SStephan Aßmus 		for (int32 i = 0; i < spanCount; i++) {
6193d2fd2acSAndrew Lindesay 			const TextSpan& span = otherParagraph.TextSpanAtIndex(i);
620f890fab6SStephan Aßmus 			paragraph.Insert(textOffset, span);
621f890fab6SStephan Aßmus 			textOffset += span.CountChars();
622f890fab6SStephan Aßmus 		}
623f890fab6SStephan Aßmus 
624dfbcbde1SAndrew Lindesay 		fParagraphs[index] = paragraph;
6254a96bcdaSStephan Aßmus 		paragraphCount++;
6264a96bcdaSStephan Aßmus 	}
6274a96bcdaSStephan Aßmus 
6284a96bcdaSStephan Aßmus 	return B_OK;
6294a96bcdaSStephan Aßmus }
6304a96bcdaSStephan Aßmus 
6314a96bcdaSStephan Aßmus 
6324a96bcdaSStephan Aßmus status_t
6334a96bcdaSStephan Aßmus TextDocument::_Remove(int32 textOffset, int32 length, int32& index,
6344a96bcdaSStephan Aßmus 	int32& paragraphCount)
6354a96bcdaSStephan Aßmus {
6364a96bcdaSStephan Aßmus 	if (length == 0)
6374a96bcdaSStephan Aßmus 		return B_OK;
6384a96bcdaSStephan Aßmus 
6394a96bcdaSStephan Aßmus 	int32 paragraphOffset;
6404a96bcdaSStephan Aßmus 	index = ParagraphIndexFor(textOffset, paragraphOffset);
6414a96bcdaSStephan Aßmus 	if (index < 0)
6424a96bcdaSStephan Aßmus 		return B_BAD_VALUE;
6434a96bcdaSStephan Aßmus 
6444a96bcdaSStephan Aßmus 	textOffset -= paragraphOffset;
6454a96bcdaSStephan Aßmus 	paragraphCount++;
6464a96bcdaSStephan Aßmus 
6474a96bcdaSStephan Aßmus 	// The paragraph at the text offset remains, even if the offset is at
6484a96bcdaSStephan Aßmus 	// the beginning of that paragraph. The idea is that the selection start
6494a96bcdaSStephan Aßmus 	// stays visually in the same place. Therefore, the paragraph at that
6504a96bcdaSStephan Aßmus 	// offset has to keep the paragraph style from that paragraph.
6514a96bcdaSStephan Aßmus 
6524a96bcdaSStephan Aßmus 	Paragraph resultParagraph(ParagraphAt(index));
6534a96bcdaSStephan Aßmus 	int32 paragraphLength = resultParagraph.Length();
6544a96bcdaSStephan Aßmus 	if (textOffset == 0 && length > paragraphLength) {
6554a96bcdaSStephan Aßmus 		length -= paragraphLength;
6564a96bcdaSStephan Aßmus 		paragraphLength = 0;
6574a96bcdaSStephan Aßmus 		resultParagraph.Clear();
6584a96bcdaSStephan Aßmus 	} else {
6594a96bcdaSStephan Aßmus 		int32 removeLength = std::min(length, paragraphLength - textOffset);
6604a96bcdaSStephan Aßmus 		resultParagraph.Remove(textOffset, removeLength);
6614a96bcdaSStephan Aßmus 		paragraphLength -= removeLength;
6624a96bcdaSStephan Aßmus 		length -= removeLength;
6634a96bcdaSStephan Aßmus 	}
6644a96bcdaSStephan Aßmus 
6654a96bcdaSStephan Aßmus 	if (textOffset == paragraphLength && length == 0
666dfbcbde1SAndrew Lindesay 		&& index + 1 < static_cast<int32>(fParagraphs.size())) {
6674a96bcdaSStephan Aßmus 		// Line break between paragraphs got removed. Shift the next
6684a96bcdaSStephan Aßmus 		// paragraph's text spans into the resulting one.
6694a96bcdaSStephan Aßmus 
6703d2fd2acSAndrew Lindesay 		const Paragraph& paragraph = ParagraphAt(index + 1);
6713d2fd2acSAndrew Lindesay 		int32 spanCount = paragraph.CountTextSpans();
6724a96bcdaSStephan Aßmus 		for (int32 i = 0; i < spanCount; i++) {
6733d2fd2acSAndrew Lindesay 			const TextSpan& span = paragraph.TextSpanAtIndex(i);
6744a96bcdaSStephan Aßmus 			resultParagraph.Append(span);
6754a96bcdaSStephan Aßmus 		}
676dfbcbde1SAndrew Lindesay 		fParagraphs.erase(fParagraphs.begin() + (index + 1));
6774a96bcdaSStephan Aßmus 		paragraphCount++;
6784a96bcdaSStephan Aßmus 	}
6794a96bcdaSStephan Aßmus 
6804a96bcdaSStephan Aßmus 	textOffset = 0;
6814a96bcdaSStephan Aßmus 
682dfbcbde1SAndrew Lindesay 	while (length > 0 && index + 1 < static_cast<int32>(fParagraphs.size())) {
6834a96bcdaSStephan Aßmus 		paragraphCount++;
6844a96bcdaSStephan Aßmus 		const Paragraph& paragraph = ParagraphAt(index + 1);
6854a96bcdaSStephan Aßmus 		paragraphLength = paragraph.Length();
6864a96bcdaSStephan Aßmus 		// Remove paragraph in any case. If some of it remains, the last
6874a96bcdaSStephan Aßmus 		// paragraph to remove is reached, and the remaining spans are
6884a96bcdaSStephan Aßmus 		// transfered to the result parahraph.
6894a96bcdaSStephan Aßmus 		if (length >= paragraphLength) {
6904a96bcdaSStephan Aßmus 			length -= paragraphLength;
691dfbcbde1SAndrew Lindesay 			fParagraphs.erase(fParagraphs.begin() + index);
6924a96bcdaSStephan Aßmus 		} else {
6934a96bcdaSStephan Aßmus 			// Last paragraph reached
6944a96bcdaSStephan Aßmus 			int32 removedLength = std::min(length, paragraphLength);
6954a96bcdaSStephan Aßmus 			Paragraph newParagraph(paragraph);
696dfbcbde1SAndrew Lindesay 			fParagraphs.erase(fParagraphs.begin() + (index + 1));
6974a96bcdaSStephan Aßmus 
6984a96bcdaSStephan Aßmus 			if (!newParagraph.Remove(0, removedLength))
6994a96bcdaSStephan Aßmus 				return B_NO_MEMORY;
7004a96bcdaSStephan Aßmus 
7014a96bcdaSStephan Aßmus 			// Transfer remaining spans to resultParagraph
7023d2fd2acSAndrew Lindesay 			int32 spanCount = newParagraph.CountTextSpans();
7034a96bcdaSStephan Aßmus 			for (int32 i = 0; i < spanCount; i++) {
7043d2fd2acSAndrew Lindesay 				const TextSpan& span = newParagraph.TextSpanAtIndex(i);
7054a96bcdaSStephan Aßmus 				resultParagraph.Append(span);
7064a96bcdaSStephan Aßmus 			}
7074a96bcdaSStephan Aßmus 
7084a96bcdaSStephan Aßmus 			break;
7094a96bcdaSStephan Aßmus 		}
7104a96bcdaSStephan Aßmus 	}
7114a96bcdaSStephan Aßmus 
712dfbcbde1SAndrew Lindesay 	fParagraphs[index] = resultParagraph;
7134a96bcdaSStephan Aßmus 
7144a96bcdaSStephan Aßmus 	return B_OK;
7154a96bcdaSStephan Aßmus }
7164a96bcdaSStephan Aßmus 
7174a96bcdaSStephan Aßmus 
7184a96bcdaSStephan Aßmus // #pragma mark - notifications
7194a96bcdaSStephan Aßmus 
7204a96bcdaSStephan Aßmus 
721d7f7bf2dSAxel Dörfler void
722d7f7bf2dSAxel Dörfler TextDocument::_NotifyTextChanging(TextChangingEvent& event) const
723d7f7bf2dSAxel Dörfler {
724d7f7bf2dSAxel Dörfler 	// Copy listener list to have a stable list in case listeners
725d7f7bf2dSAxel Dörfler 	// are added/removed from within the notification hook.
726dfbcbde1SAndrew Lindesay 	std::vector<TextListenerRef> listeners(fTextListeners);
727dfbcbde1SAndrew Lindesay 
728dfbcbde1SAndrew Lindesay 	int32 count = listeners.size();
729d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
730dfbcbde1SAndrew Lindesay 		const TextListenerRef& listener = listeners[i];
731779ab335SX512 		if (!listener.IsSet())
732d7f7bf2dSAxel Dörfler 			continue;
733d7f7bf2dSAxel Dörfler 		listener->TextChanging(event);
734d7f7bf2dSAxel Dörfler 		if (event.IsCanceled())
735d7f7bf2dSAxel Dörfler 			break;
736d7f7bf2dSAxel Dörfler 	}
737d7f7bf2dSAxel Dörfler }
738d7f7bf2dSAxel Dörfler 
739d7f7bf2dSAxel Dörfler 
740d7f7bf2dSAxel Dörfler void
741d7f7bf2dSAxel Dörfler TextDocument::_NotifyTextChanged(const TextChangedEvent& event) const
742d7f7bf2dSAxel Dörfler {
743d7f7bf2dSAxel Dörfler 	// Copy listener list to have a stable list in case listeners
744d7f7bf2dSAxel Dörfler 	// are added/removed from within the notification hook.
745dfbcbde1SAndrew Lindesay 	std::vector<TextListenerRef> listeners(fTextListeners);
746dfbcbde1SAndrew Lindesay 	int32 count = listeners.size();
747d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
748dfbcbde1SAndrew Lindesay 		const TextListenerRef& listener = listeners[i];
749779ab335SX512 		if (!listener.IsSet())
750d7f7bf2dSAxel Dörfler 			continue;
751d7f7bf2dSAxel Dörfler 		listener->TextChanged(event);
752d7f7bf2dSAxel Dörfler 	}
753d7f7bf2dSAxel Dörfler }
754d7f7bf2dSAxel Dörfler 
7554bf45bfbSStephan Aßmus 
7564bf45bfbSStephan Aßmus void
7574bf45bfbSStephan Aßmus TextDocument::_NotifyUndoableEditHappened(const UndoableEditRef& edit) const
7584bf45bfbSStephan Aßmus {
7594bf45bfbSStephan Aßmus 	// Copy listener list to have a stable list in case listeners
7604bf45bfbSStephan Aßmus 	// are added/removed from within the notification hook.
761dfbcbde1SAndrew Lindesay 	std::vector<UndoableEditListenerRef> listeners(fUndoListeners);
762dfbcbde1SAndrew Lindesay 	int32 count = listeners.size();
7634bf45bfbSStephan Aßmus 	for (int32 i = 0; i < count; i++) {
764dfbcbde1SAndrew Lindesay 		const UndoableEditListenerRef& listener = listeners[i];
765779ab335SX512 		if (!listener.IsSet())
7664bf45bfbSStephan Aßmus 			continue;
7674bf45bfbSStephan Aßmus 		listener->UndoableEditHappened(this, edit);
7684bf45bfbSStephan Aßmus 	}
7694bf45bfbSStephan Aßmus }
770