xref: /haiku/src/apps/haikudepot/textview/TextDocument.cpp (revision 5f80d48a76353e7bf18a7ad69462974e0db1d455)
1d7f7bf2dSAxel Dörfler /*
2d7f7bf2dSAxel Dörfler  * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
3d7f7bf2dSAxel Dörfler  * All rights reserved. Distributed under the terms of the MIT License.
4d7f7bf2dSAxel Dörfler  */
5d7f7bf2dSAxel Dörfler 
6d7f7bf2dSAxel Dörfler #include "TextDocument.h"
7d7f7bf2dSAxel Dörfler 
8d7f7bf2dSAxel Dörfler #include <algorithm>
9d7f7bf2dSAxel Dörfler #include <stdio.h>
10d7f7bf2dSAxel Dörfler 
11d7f7bf2dSAxel Dörfler 
12d7f7bf2dSAxel Dörfler TextDocument::TextDocument()
13d7f7bf2dSAxel Dörfler 	:
14d7f7bf2dSAxel Dörfler 	fParagraphs(),
15d7f7bf2dSAxel Dörfler 	fEmptyLastParagraph(),
16d7f7bf2dSAxel Dörfler 	fDefaultCharacterStyle()
17d7f7bf2dSAxel Dörfler {
18d7f7bf2dSAxel Dörfler }
19d7f7bf2dSAxel Dörfler 
20d7f7bf2dSAxel Dörfler 
21f890fab6SStephan Aßmus TextDocument::TextDocument(CharacterStyle characterStyle,
22f890fab6SStephan Aßmus 	ParagraphStyle paragraphStyle)
23d7f7bf2dSAxel Dörfler 	:
24d7f7bf2dSAxel Dörfler 	fParagraphs(),
25d7f7bf2dSAxel Dörfler 	fEmptyLastParagraph(paragraphStyle),
26d7f7bf2dSAxel Dörfler 	fDefaultCharacterStyle(characterStyle)
27d7f7bf2dSAxel Dörfler {
28d7f7bf2dSAxel Dörfler }
29d7f7bf2dSAxel Dörfler 
30d7f7bf2dSAxel Dörfler 
31d7f7bf2dSAxel Dörfler TextDocument::TextDocument(const TextDocument& other)
32d7f7bf2dSAxel Dörfler 	:
33d7f7bf2dSAxel Dörfler 	fParagraphs(other.fParagraphs),
34d7f7bf2dSAxel Dörfler 	fEmptyLastParagraph(other.fEmptyLastParagraph),
35d7f7bf2dSAxel Dörfler 	fDefaultCharacterStyle(other.fDefaultCharacterStyle)
36d7f7bf2dSAxel Dörfler {
37d7f7bf2dSAxel Dörfler }
38d7f7bf2dSAxel Dörfler 
39d7f7bf2dSAxel Dörfler 
40d7f7bf2dSAxel Dörfler TextDocument&
41d7f7bf2dSAxel Dörfler TextDocument::operator=(const TextDocument& other)
42d7f7bf2dSAxel Dörfler {
43d7f7bf2dSAxel Dörfler 	fParagraphs = other.fParagraphs;
44d7f7bf2dSAxel Dörfler 	fEmptyLastParagraph = other.fEmptyLastParagraph;
45d7f7bf2dSAxel Dörfler 	fDefaultCharacterStyle = other.fDefaultCharacterStyle;
46d7f7bf2dSAxel Dörfler 
47d7f7bf2dSAxel Dörfler 	return *this;
48d7f7bf2dSAxel Dörfler }
49d7f7bf2dSAxel Dörfler 
50d7f7bf2dSAxel Dörfler 
51d7f7bf2dSAxel Dörfler bool
52d7f7bf2dSAxel Dörfler TextDocument::operator==(const TextDocument& other) const
53d7f7bf2dSAxel Dörfler {
54d7f7bf2dSAxel Dörfler 	if (this == &other)
55d7f7bf2dSAxel Dörfler 		return true;
56d7f7bf2dSAxel Dörfler 
57d7f7bf2dSAxel Dörfler 	return fEmptyLastParagraph == other.fEmptyLastParagraph
58d7f7bf2dSAxel Dörfler 		&& fDefaultCharacterStyle == other.fDefaultCharacterStyle
59d7f7bf2dSAxel Dörfler 		&& fParagraphs == other.fParagraphs;
60d7f7bf2dSAxel Dörfler }
61d7f7bf2dSAxel Dörfler 
62d7f7bf2dSAxel Dörfler 
63d7f7bf2dSAxel Dörfler bool
64d7f7bf2dSAxel Dörfler TextDocument::operator!=(const TextDocument& other) const
65d7f7bf2dSAxel Dörfler {
66d7f7bf2dSAxel Dörfler 	return !(*this == other);
67d7f7bf2dSAxel Dörfler }
68d7f7bf2dSAxel Dörfler 
69d7f7bf2dSAxel Dörfler 
70d7f7bf2dSAxel Dörfler // #pragma mark -
71d7f7bf2dSAxel Dörfler 
72d7f7bf2dSAxel Dörfler 
73d7f7bf2dSAxel Dörfler status_t
74d7f7bf2dSAxel Dörfler TextDocument::Insert(int32 textOffset, const BString& text)
75d7f7bf2dSAxel Dörfler {
764a96bcdaSStephan Aßmus 	return Replace(textOffset, 0, text);
77d7f7bf2dSAxel Dörfler }
78d7f7bf2dSAxel Dörfler 
79d7f7bf2dSAxel Dörfler 
80d7f7bf2dSAxel Dörfler status_t
81d7f7bf2dSAxel Dörfler TextDocument::Insert(int32 textOffset, const BString& text,
82f890fab6SStephan Aßmus 	CharacterStyle style)
83d7f7bf2dSAxel Dörfler {
844a96bcdaSStephan Aßmus 	return Replace(textOffset, 0, text, style);
85d7f7bf2dSAxel Dörfler }
86d7f7bf2dSAxel Dörfler 
87d7f7bf2dSAxel Dörfler 
88d7f7bf2dSAxel Dörfler status_t
89d7f7bf2dSAxel Dörfler TextDocument::Insert(int32 textOffset, const BString& text,
90f890fab6SStephan Aßmus 	CharacterStyle characterStyle, ParagraphStyle paragraphStyle)
91d7f7bf2dSAxel Dörfler {
924a96bcdaSStephan Aßmus 	return Replace(textOffset, 0, text, characterStyle, paragraphStyle);
93d7f7bf2dSAxel Dörfler }
94d7f7bf2dSAxel Dörfler 
95d7f7bf2dSAxel Dörfler 
96d7f7bf2dSAxel Dörfler // #pragma mark -
97d7f7bf2dSAxel Dörfler 
98d7f7bf2dSAxel Dörfler 
99d7f7bf2dSAxel Dörfler status_t
100d7f7bf2dSAxel Dörfler TextDocument::Remove(int32 textOffset, int32 length)
101d7f7bf2dSAxel Dörfler {
1024a96bcdaSStephan Aßmus 	return Replace(textOffset, length, BString());
103d7f7bf2dSAxel Dörfler }
104d7f7bf2dSAxel Dörfler 
105d7f7bf2dSAxel Dörfler 
106d7f7bf2dSAxel Dörfler // #pragma mark -
107d7f7bf2dSAxel Dörfler 
108d7f7bf2dSAxel Dörfler 
109d7f7bf2dSAxel Dörfler status_t
110d7f7bf2dSAxel Dörfler TextDocument::Replace(int32 textOffset, int32 length, const BString& text)
111d7f7bf2dSAxel Dörfler {
112d7f7bf2dSAxel Dörfler 	return Replace(textOffset, length, text, CharacterStyleAt(textOffset));
113d7f7bf2dSAxel Dörfler }
114d7f7bf2dSAxel Dörfler 
115d7f7bf2dSAxel Dörfler 
116d7f7bf2dSAxel Dörfler status_t
117d7f7bf2dSAxel Dörfler TextDocument::Replace(int32 textOffset, int32 length, const BString& text,
118f890fab6SStephan Aßmus 	CharacterStyle style)
119d7f7bf2dSAxel Dörfler {
120d7f7bf2dSAxel Dörfler 	return Replace(textOffset, length, text, style,
121d7f7bf2dSAxel Dörfler 		ParagraphStyleAt(textOffset));
122d7f7bf2dSAxel Dörfler }
123d7f7bf2dSAxel Dörfler 
124d7f7bf2dSAxel Dörfler 
125d7f7bf2dSAxel Dörfler status_t
126d7f7bf2dSAxel Dörfler TextDocument::Replace(int32 textOffset, int32 length, const BString& text,
127f890fab6SStephan Aßmus 	CharacterStyle characterStyle, ParagraphStyle paragraphStyle)
128f890fab6SStephan Aßmus {
129f890fab6SStephan Aßmus 	TextDocumentRef document = NormalizeText(text, characterStyle,
130f890fab6SStephan Aßmus 		paragraphStyle);
131f890fab6SStephan Aßmus 	if (document.Get() == NULL || document->Length() != text.CountChars())
132f890fab6SStephan Aßmus 		return B_NO_MEMORY;
133f890fab6SStephan Aßmus 	return Replace(textOffset, length, document);
134f890fab6SStephan Aßmus }
135f890fab6SStephan Aßmus 
136f890fab6SStephan Aßmus 
137f890fab6SStephan Aßmus status_t
138f890fab6SStephan Aßmus TextDocument::Replace(int32 textOffset, int32 length, TextDocumentRef document)
139d7f7bf2dSAxel Dörfler {
1404a96bcdaSStephan Aßmus 	int32 firstParagraph = 0;
1414a96bcdaSStephan Aßmus 	int32 paragraphCount = 0;
1424a96bcdaSStephan Aßmus 
1434a96bcdaSStephan Aßmus 	// TODO: Call _NotifyTextChanging() before any change happened
1444a96bcdaSStephan Aßmus 
1454a96bcdaSStephan Aßmus 	status_t ret = _Remove(textOffset, length, firstParagraph, paragraphCount);
146d7f7bf2dSAxel Dörfler 	if (ret != B_OK)
147d7f7bf2dSAxel Dörfler 		return ret;
148d7f7bf2dSAxel Dörfler 
149f890fab6SStephan Aßmus 	ret = _Insert(textOffset, document, firstParagraph, paragraphCount);
1504a96bcdaSStephan Aßmus 
1514a96bcdaSStephan Aßmus 	_NotifyTextChanged(TextChangedEvent(firstParagraph, paragraphCount));
1524a96bcdaSStephan Aßmus 
1534a96bcdaSStephan Aßmus 	return ret;
154d7f7bf2dSAxel Dörfler }
155d7f7bf2dSAxel Dörfler 
156d7f7bf2dSAxel Dörfler 
157d7f7bf2dSAxel Dörfler // #pragma mark -
158d7f7bf2dSAxel Dörfler 
159d7f7bf2dSAxel Dörfler 
160d7f7bf2dSAxel Dörfler const CharacterStyle&
161d7f7bf2dSAxel Dörfler TextDocument::CharacterStyleAt(int32 textOffset) const
162d7f7bf2dSAxel Dörfler {
163d7f7bf2dSAxel Dörfler 	int32 paragraphOffset;
164d7f7bf2dSAxel Dörfler 	const Paragraph& paragraph = ParagraphAt(textOffset, paragraphOffset);
165d7f7bf2dSAxel Dörfler 
166d7f7bf2dSAxel Dörfler 	textOffset -= paragraphOffset;
167d7f7bf2dSAxel Dörfler 	const TextSpanList& spans = paragraph.TextSpans();
168d7f7bf2dSAxel Dörfler 
169d7f7bf2dSAxel Dörfler 	int32 index = 0;
170d7f7bf2dSAxel Dörfler 	while (index < spans.CountItems()) {
171d7f7bf2dSAxel Dörfler 		const TextSpan& span = spans.ItemAtFast(index);
172d7f7bf2dSAxel Dörfler 		if (textOffset - span.CountChars() < 0)
173d7f7bf2dSAxel Dörfler 			return span.Style();
174d7f7bf2dSAxel Dörfler 		textOffset -= span.CountChars();
175d7f7bf2dSAxel Dörfler 		index++;
176d7f7bf2dSAxel Dörfler 	}
177d7f7bf2dSAxel Dörfler 
178d7f7bf2dSAxel Dörfler 	return fDefaultCharacterStyle;
179d7f7bf2dSAxel Dörfler }
180d7f7bf2dSAxel Dörfler 
181d7f7bf2dSAxel Dörfler 
182d7f7bf2dSAxel Dörfler const ParagraphStyle&
183d7f7bf2dSAxel Dörfler TextDocument::ParagraphStyleAt(int32 textOffset) const
184d7f7bf2dSAxel Dörfler {
185d7f7bf2dSAxel Dörfler 	int32 paragraphOffset;
186d7f7bf2dSAxel Dörfler 	return ParagraphAt(textOffset, paragraphOffset).Style();
187d7f7bf2dSAxel Dörfler }
188d7f7bf2dSAxel Dörfler 
189d7f7bf2dSAxel Dörfler 
190d7f7bf2dSAxel Dörfler // #pragma mark -
191d7f7bf2dSAxel Dörfler 
192d7f7bf2dSAxel Dörfler 
193d7f7bf2dSAxel Dörfler int32
194f890fab6SStephan Aßmus TextDocument::CountParagraphs() const
195f890fab6SStephan Aßmus {
196f890fab6SStephan Aßmus 	return fParagraphs.CountItems();
197f890fab6SStephan Aßmus }
198f890fab6SStephan Aßmus 
199f890fab6SStephan Aßmus 
200f890fab6SStephan Aßmus int32
201d7f7bf2dSAxel Dörfler TextDocument::ParagraphIndexFor(int32 textOffset, int32& paragraphOffset) const
202d7f7bf2dSAxel Dörfler {
203d7f7bf2dSAxel Dörfler 	// TODO: Could binary search the Paragraphs if they were wrapped in classes
204d7f7bf2dSAxel Dörfler 	// that knew there text offset in the document.
205d7f7bf2dSAxel Dörfler 	int32 textLength = 0;
206d7f7bf2dSAxel Dörfler 	paragraphOffset = 0;
207d7f7bf2dSAxel Dörfler 	int32 count = fParagraphs.CountItems();
208d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
209d7f7bf2dSAxel Dörfler 		const Paragraph& paragraph = fParagraphs.ItemAtFast(i);
210d7f7bf2dSAxel Dörfler 		int32 paragraphLength = paragraph.Length();
211d7f7bf2dSAxel Dörfler 		textLength += paragraphLength;
212d7f7bf2dSAxel Dörfler 		if (textLength > textOffset
213d7f7bf2dSAxel Dörfler 			|| (i == count - 1 && textLength == textOffset)) {
214d7f7bf2dSAxel Dörfler 			return i;
215d7f7bf2dSAxel Dörfler 		}
216d7f7bf2dSAxel Dörfler 		paragraphOffset += paragraphLength;
217d7f7bf2dSAxel Dörfler 	}
218d7f7bf2dSAxel Dörfler 	return -1;
219d7f7bf2dSAxel Dörfler }
220d7f7bf2dSAxel Dörfler 
221d7f7bf2dSAxel Dörfler 
222d7f7bf2dSAxel Dörfler const Paragraph&
223d7f7bf2dSAxel Dörfler TextDocument::ParagraphAt(int32 textOffset, int32& paragraphOffset) const
224d7f7bf2dSAxel Dörfler {
225d7f7bf2dSAxel Dörfler 	int32 index = ParagraphIndexFor(textOffset, paragraphOffset);
226d7f7bf2dSAxel Dörfler 	if (index >= 0)
227d7f7bf2dSAxel Dörfler 		return fParagraphs.ItemAtFast(index);
228d7f7bf2dSAxel Dörfler 
229d7f7bf2dSAxel Dörfler 	return fEmptyLastParagraph;
230d7f7bf2dSAxel Dörfler }
231d7f7bf2dSAxel Dörfler 
232d7f7bf2dSAxel Dörfler 
233d7f7bf2dSAxel Dörfler const Paragraph&
234d7f7bf2dSAxel Dörfler TextDocument::ParagraphAt(int32 index) const
235d7f7bf2dSAxel Dörfler {
236d7f7bf2dSAxel Dörfler 	if (index >= 0 && index < fParagraphs.CountItems())
237d7f7bf2dSAxel Dörfler 		return fParagraphs.ItemAtFast(index);
238d7f7bf2dSAxel Dörfler 	return fEmptyLastParagraph;
239d7f7bf2dSAxel Dörfler }
240d7f7bf2dSAxel Dörfler 
241d7f7bf2dSAxel Dörfler 
242d7f7bf2dSAxel Dörfler bool
243d7f7bf2dSAxel Dörfler TextDocument::Append(const Paragraph& paragraph)
244d7f7bf2dSAxel Dörfler {
245d7f7bf2dSAxel Dörfler 	return fParagraphs.Add(paragraph);
246d7f7bf2dSAxel Dörfler }
247d7f7bf2dSAxel Dörfler 
248d7f7bf2dSAxel Dörfler 
249d7f7bf2dSAxel Dörfler int32
250d7f7bf2dSAxel Dörfler TextDocument::Length() const
251d7f7bf2dSAxel Dörfler {
252d7f7bf2dSAxel Dörfler 	// TODO: Could be O(1) if the Paragraphs were wrapped in classes that
2534a96bcdaSStephan Aßmus 	// knew their text offset in the document.
254d7f7bf2dSAxel Dörfler 	int32 textLength = 0;
255d7f7bf2dSAxel Dörfler 	int32 count = fParagraphs.CountItems();
256d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
257d7f7bf2dSAxel Dörfler 		const Paragraph& paragraph = fParagraphs.ItemAtFast(i);
258d7f7bf2dSAxel Dörfler 		textLength += paragraph.Length();
259d7f7bf2dSAxel Dörfler 	}
260d7f7bf2dSAxel Dörfler 	return textLength;
261d7f7bf2dSAxel Dörfler }
262d7f7bf2dSAxel Dörfler 
263d7f7bf2dSAxel Dörfler 
264d7f7bf2dSAxel Dörfler BString
265d7f7bf2dSAxel Dörfler TextDocument::Text() const
266d7f7bf2dSAxel Dörfler {
267d7f7bf2dSAxel Dörfler 	return Text(0, Length());
268d7f7bf2dSAxel Dörfler }
269d7f7bf2dSAxel Dörfler 
270d7f7bf2dSAxel Dörfler 
271d7f7bf2dSAxel Dörfler BString
272d7f7bf2dSAxel Dörfler TextDocument::Text(int32 start, int32 length) const
273d7f7bf2dSAxel Dörfler {
274d7f7bf2dSAxel Dörfler 	if (start < 0)
275d7f7bf2dSAxel Dörfler 		start = 0;
276d7f7bf2dSAxel Dörfler 
277d7f7bf2dSAxel Dörfler 	BString text;
278d7f7bf2dSAxel Dörfler 
279d7f7bf2dSAxel Dörfler 	int32 count = fParagraphs.CountItems();
280d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
281d7f7bf2dSAxel Dörfler 		const Paragraph& paragraph = fParagraphs.ItemAtFast(i);
282d7f7bf2dSAxel Dörfler 		int32 paragraphLength = paragraph.Length();
283d7f7bf2dSAxel Dörfler 		if (paragraphLength == 0)
284d7f7bf2dSAxel Dörfler 			continue;
285d7f7bf2dSAxel Dörfler 		if (start > paragraphLength) {
286d7f7bf2dSAxel Dörfler 			// Skip paragraph if its before start
287d7f7bf2dSAxel Dörfler 			start -= paragraphLength;
288d7f7bf2dSAxel Dörfler 			continue;
289d7f7bf2dSAxel Dörfler 		}
290d7f7bf2dSAxel Dörfler 
291d7f7bf2dSAxel Dörfler 		// Remaining paragraph length after start
292d7f7bf2dSAxel Dörfler 		paragraphLength -= start;
293d7f7bf2dSAxel Dörfler 		int32 copyLength = std::min(paragraphLength, length);
294d7f7bf2dSAxel Dörfler 
295d7f7bf2dSAxel Dörfler 		text << paragraph.Text(start, copyLength);
296d7f7bf2dSAxel Dörfler 
297d7f7bf2dSAxel Dörfler 		length -= copyLength;
298d7f7bf2dSAxel Dörfler 		if (length == 0)
299d7f7bf2dSAxel Dörfler 			break;
300d7f7bf2dSAxel Dörfler 
301d7f7bf2dSAxel Dörfler 		// Next paragraph is copied from its beginning
302d7f7bf2dSAxel Dörfler 		start = 0;
303d7f7bf2dSAxel Dörfler 	}
304d7f7bf2dSAxel Dörfler 
305d7f7bf2dSAxel Dörfler 	return text;
306d7f7bf2dSAxel Dörfler }
307d7f7bf2dSAxel Dörfler 
308d7f7bf2dSAxel Dörfler 
309d7f7bf2dSAxel Dörfler TextDocumentRef
310d7f7bf2dSAxel Dörfler TextDocument::SubDocument(int32 start, int32 length) const
311d7f7bf2dSAxel Dörfler {
312d7f7bf2dSAxel Dörfler 	TextDocumentRef result(new(std::nothrow) TextDocument(
313d7f7bf2dSAxel Dörfler 		fDefaultCharacterStyle, fEmptyLastParagraph.Style()), true);
314d7f7bf2dSAxel Dörfler 
315d7f7bf2dSAxel Dörfler 	if (result.Get() == NULL)
316d7f7bf2dSAxel Dörfler 		return result;
317d7f7bf2dSAxel Dörfler 
318d7f7bf2dSAxel Dörfler 	if (start < 0)
319d7f7bf2dSAxel Dörfler 		start = 0;
320d7f7bf2dSAxel Dörfler 
321d7f7bf2dSAxel Dörfler 	int32 count = fParagraphs.CountItems();
322d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
323d7f7bf2dSAxel Dörfler 		const Paragraph& paragraph = fParagraphs.ItemAtFast(i);
324d7f7bf2dSAxel Dörfler 		int32 paragraphLength = paragraph.Length();
325d7f7bf2dSAxel Dörfler 		if (paragraphLength == 0)
326d7f7bf2dSAxel Dörfler 			continue;
327d7f7bf2dSAxel Dörfler 		if (start > paragraphLength) {
328d7f7bf2dSAxel Dörfler 			// Skip paragraph if its before start
329d7f7bf2dSAxel Dörfler 			start -= paragraphLength;
330d7f7bf2dSAxel Dörfler 			continue;
331d7f7bf2dSAxel Dörfler 		}
332d7f7bf2dSAxel Dörfler 
333d7f7bf2dSAxel Dörfler 		// Remaining paragraph length after start
334d7f7bf2dSAxel Dörfler 		paragraphLength -= start;
335d7f7bf2dSAxel Dörfler 		int32 copyLength = std::min(paragraphLength, length);
336d7f7bf2dSAxel Dörfler 
337d7f7bf2dSAxel Dörfler 		result->Append(paragraph.SubParagraph(start, copyLength));
338d7f7bf2dSAxel Dörfler 
339d7f7bf2dSAxel Dörfler 		length -= copyLength;
340d7f7bf2dSAxel Dörfler 		if (length == 0)
341d7f7bf2dSAxel Dörfler 			break;
342d7f7bf2dSAxel Dörfler 
343d7f7bf2dSAxel Dörfler 		// Next paragraph is copied from its beginning
344d7f7bf2dSAxel Dörfler 		start = 0;
345d7f7bf2dSAxel Dörfler 	}
346d7f7bf2dSAxel Dörfler 
347d7f7bf2dSAxel Dörfler 	return result;
348d7f7bf2dSAxel Dörfler }
349d7f7bf2dSAxel Dörfler 
350d7f7bf2dSAxel Dörfler 
351d7f7bf2dSAxel Dörfler // #pragma mark -
352d7f7bf2dSAxel Dörfler 
353d7f7bf2dSAxel Dörfler 
354d7f7bf2dSAxel Dörfler void
355d7f7bf2dSAxel Dörfler TextDocument::PrintToStream() const
356d7f7bf2dSAxel Dörfler {
357d7f7bf2dSAxel Dörfler 	int32 paragraphCount = fParagraphs.CountItems();
358d7f7bf2dSAxel Dörfler 	if (paragraphCount == 0) {
359d7f7bf2dSAxel Dörfler 		printf("<document/>\n");
360d7f7bf2dSAxel Dörfler 		return;
361d7f7bf2dSAxel Dörfler 	}
362d7f7bf2dSAxel Dörfler 	printf("<document>\n");
363d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < paragraphCount; i++) {
364d7f7bf2dSAxel Dörfler 		fParagraphs.ItemAtFast(i).PrintToStream();
365d7f7bf2dSAxel Dörfler 	}
366d7f7bf2dSAxel Dörfler 	printf("</document>\n");
367d7f7bf2dSAxel Dörfler }
368d7f7bf2dSAxel Dörfler 
369d7f7bf2dSAxel Dörfler 
370f890fab6SStephan Aßmus /*static*/ TextDocumentRef
371f890fab6SStephan Aßmus TextDocument::NormalizeText(const BString& text,
372f890fab6SStephan Aßmus 	CharacterStyle characterStyle, ParagraphStyle paragraphStyle)
373d7f7bf2dSAxel Dörfler {
374f890fab6SStephan Aßmus 	TextDocumentRef document(new(std::nothrow) TextDocument(characterStyle,
375f890fab6SStephan Aßmus 			paragraphStyle), true);
376f890fab6SStephan Aßmus 	if (document.Get() == NULL)
377f890fab6SStephan Aßmus 		throw B_NO_MEMORY;
378d7f7bf2dSAxel Dörfler 
379f890fab6SStephan Aßmus 	Paragraph paragraph(paragraphStyle);
380d7f7bf2dSAxel Dörfler 
381f890fab6SStephan Aßmus 	// Append TextSpans, splitting 'text' into Paragraphs at line breaks.
3824a96bcdaSStephan Aßmus 	int32 length = text.CountChars();
3834a96bcdaSStephan Aßmus 	int32 chunkStart = 0;
3844a96bcdaSStephan Aßmus 	while (chunkStart < length) {
3854a96bcdaSStephan Aßmus 		int32 chunkEnd = text.FindFirst('\n', chunkStart);
3864a96bcdaSStephan Aßmus 		bool foundLineBreak = chunkEnd >= chunkStart;
3874a96bcdaSStephan Aßmus 		if (foundLineBreak)
3884a96bcdaSStephan Aßmus 			chunkEnd++;
3894a96bcdaSStephan Aßmus 		else
3904a96bcdaSStephan Aßmus 			chunkEnd = length;
3914a96bcdaSStephan Aßmus 
3924a96bcdaSStephan Aßmus 		BString chunk;
3934a96bcdaSStephan Aßmus 		text.CopyCharsInto(chunk, chunkStart, chunkEnd - chunkStart);
3944a96bcdaSStephan Aßmus 		TextSpan span(chunk, characterStyle);
3954a96bcdaSStephan Aßmus 
396f890fab6SStephan Aßmus 		if (!paragraph.Append(span))
397f890fab6SStephan Aßmus 			throw B_NO_MEMORY;
398f890fab6SStephan Aßmus 		if (paragraph.Length() > 0 && !document->Append(paragraph))
399f890fab6SStephan Aßmus 			throw B_NO_MEMORY;
4004a96bcdaSStephan Aßmus 
401f890fab6SStephan Aßmus 		paragraph = Paragraph(paragraphStyle);
4024a96bcdaSStephan Aßmus 		chunkStart = chunkEnd + 1;
4034a96bcdaSStephan Aßmus 	}
4044a96bcdaSStephan Aßmus 
405f890fab6SStephan Aßmus 	return document;
406f890fab6SStephan Aßmus }
407f890fab6SStephan Aßmus 
408f890fab6SStephan Aßmus 
409f890fab6SStephan Aßmus // #pragma mark -
410f890fab6SStephan Aßmus 
411f890fab6SStephan Aßmus 
412f890fab6SStephan Aßmus bool
413f890fab6SStephan Aßmus TextDocument::AddListener(TextListenerRef listener)
414f890fab6SStephan Aßmus {
415f890fab6SStephan Aßmus 	return fTextListeners.Add(listener);
416f890fab6SStephan Aßmus }
417f890fab6SStephan Aßmus 
418f890fab6SStephan Aßmus 
419f890fab6SStephan Aßmus bool
420f890fab6SStephan Aßmus TextDocument::RemoveListener(TextListenerRef listener)
421f890fab6SStephan Aßmus {
422f890fab6SStephan Aßmus 	return fTextListeners.Remove(listener);
423f890fab6SStephan Aßmus }
424f890fab6SStephan Aßmus 
425f890fab6SStephan Aßmus 
426f890fab6SStephan Aßmus bool
427f890fab6SStephan Aßmus TextDocument::AddUndoListener(UndoableEditListenerRef listener)
428f890fab6SStephan Aßmus {
429f890fab6SStephan Aßmus 	return fUndoListeners.Add(listener);
430f890fab6SStephan Aßmus }
431f890fab6SStephan Aßmus 
432f890fab6SStephan Aßmus 
433f890fab6SStephan Aßmus bool
434f890fab6SStephan Aßmus TextDocument::RemoveUndoListener(UndoableEditListenerRef listener)
435f890fab6SStephan Aßmus {
436f890fab6SStephan Aßmus 	return fUndoListeners.Remove(listener);
437f890fab6SStephan Aßmus }
438f890fab6SStephan Aßmus 
439f890fab6SStephan Aßmus 
440f890fab6SStephan Aßmus // #pragma mark - private
441f890fab6SStephan Aßmus 
442f890fab6SStephan Aßmus 
443f890fab6SStephan Aßmus status_t
444f890fab6SStephan Aßmus TextDocument::_Insert(int32 textOffset, TextDocumentRef document,
445f890fab6SStephan Aßmus 	int32& index, int32& paragraphCount)
446f890fab6SStephan Aßmus {
447f890fab6SStephan Aßmus 	int32 paragraphOffset;
448f890fab6SStephan Aßmus 	index = ParagraphIndexFor(textOffset, paragraphOffset);
449f890fab6SStephan Aßmus 	if (index < 0)
450f890fab6SStephan Aßmus 		return B_BAD_VALUE;
451f890fab6SStephan Aßmus 
452f890fab6SStephan Aßmus 	if (document->Length() == 0)
453f890fab6SStephan Aßmus 		return B_OK;
454f890fab6SStephan Aßmus 
455f890fab6SStephan Aßmus 	textOffset -= paragraphOffset;
456f890fab6SStephan Aßmus 
457f890fab6SStephan Aßmus 	bool hasLineBreaks;
458f890fab6SStephan Aßmus 	if (document->CountParagraphs() > 1) {
459f890fab6SStephan Aßmus 		hasLineBreaks = true;
460f890fab6SStephan Aßmus 	} else {
461f890fab6SStephan Aßmus 		const Paragraph& paragraph = document->ParagraphAt(0);
462f890fab6SStephan Aßmus 		hasLineBreaks = paragraph.EndsWith("\n");
463f890fab6SStephan Aßmus 	}
464f890fab6SStephan Aßmus 
465f890fab6SStephan Aßmus 	if (hasLineBreaks) {
466f890fab6SStephan Aßmus 		// Split paragraph at textOffset
467f890fab6SStephan Aßmus 		Paragraph paragraph1(ParagraphAt(index).Style());
468f890fab6SStephan Aßmus 		Paragraph paragraph2(document->ParagraphAt(
469f890fab6SStephan Aßmus 			document->CountParagraphs() - 1).Style());
470f890fab6SStephan Aßmus 		{
471f890fab6SStephan Aßmus 			const TextSpanList& textSpans = ParagraphAt(index).TextSpans();
472f890fab6SStephan Aßmus 			int32 spanCount = textSpans.CountItems();
473f890fab6SStephan Aßmus 			for (int32 i = 0; i < spanCount; i++) {
474f890fab6SStephan Aßmus 				const TextSpan& span = textSpans.ItemAtFast(i);
475f890fab6SStephan Aßmus 				int32 spanLength = span.CountChars();
476f890fab6SStephan Aßmus 				if (textOffset >= spanLength) {
477f890fab6SStephan Aßmus 					if (!paragraph1.Append(span))
478f890fab6SStephan Aßmus 						return B_NO_MEMORY;
479f890fab6SStephan Aßmus 					textOffset -= spanLength;
480f890fab6SStephan Aßmus 				} else if (textOffset > 0) {
481f890fab6SStephan Aßmus 					if (!paragraph1.Append(
482f890fab6SStephan Aßmus 							span.SubSpan(0, textOffset))
483f890fab6SStephan Aßmus 						|| !paragraph2.Append(
484f890fab6SStephan Aßmus 							span.SubSpan(textOffset,
485f890fab6SStephan Aßmus 								spanLength - textOffset))) {
486f890fab6SStephan Aßmus 						return B_NO_MEMORY;
487f890fab6SStephan Aßmus 					}
488f890fab6SStephan Aßmus 					textOffset = 0;
489f890fab6SStephan Aßmus 				} else {
490f890fab6SStephan Aßmus 					if (!paragraph2.Append(span))
491f890fab6SStephan Aßmus 						return B_NO_MEMORY;
492f890fab6SStephan Aßmus 				}
493f890fab6SStephan Aßmus 			}
494f890fab6SStephan Aßmus 		}
495f890fab6SStephan Aßmus 
496f890fab6SStephan Aßmus 		fParagraphs.Remove(index);
497f890fab6SStephan Aßmus 
498f890fab6SStephan Aßmus 		// Append first paragraph in other document to first part of
499f890fab6SStephan Aßmus 		// paragraph at insert position
500f890fab6SStephan Aßmus 		{
501f890fab6SStephan Aßmus 			const Paragraph& otherParagraph = document->ParagraphAt(0);
502f890fab6SStephan Aßmus 			const TextSpanList& textSpans = otherParagraph.TextSpans();
503f890fab6SStephan Aßmus 			int32 spanCount = textSpans.CountItems();
504f890fab6SStephan Aßmus 			for (int32 i = 0; i < spanCount; i++) {
505f890fab6SStephan Aßmus 				const TextSpan& span = textSpans.ItemAtFast(i);
506f890fab6SStephan Aßmus 				// TODO: Import/map CharacterStyles
507f890fab6SStephan Aßmus 				if (!paragraph1.Append(span))
508f890fab6SStephan Aßmus 					return B_NO_MEMORY;
509f890fab6SStephan Aßmus 			}
510f890fab6SStephan Aßmus 		}
511f890fab6SStephan Aßmus 
512f890fab6SStephan Aßmus 		// Insert the first paragraph-part again to the document
513f890fab6SStephan Aßmus 		if (!fParagraphs.Add(paragraph1, index))
514f890fab6SStephan Aßmus 			return B_NO_MEMORY;
515f890fab6SStephan Aßmus 		paragraphCount++;
516f890fab6SStephan Aßmus 
517f890fab6SStephan Aßmus 		// Insert the other document's paragraph save for the last one
518f890fab6SStephan Aßmus 		for (int32 i = 1; i < document->CountParagraphs() - 1; i++) {
519f890fab6SStephan Aßmus 			const Paragraph& otherParagraph = document->ParagraphAt(i);
520f890fab6SStephan Aßmus 			// TODO: Import/map CharacterStyles and ParagraphStyle
521f890fab6SStephan Aßmus 			if (!fParagraphs.Add(otherParagraph, ++index))
522f890fab6SStephan Aßmus 				return B_NO_MEMORY;
523f890fab6SStephan Aßmus 			paragraphCount++;
524f890fab6SStephan Aßmus 		}
525f890fab6SStephan Aßmus 
526f890fab6SStephan Aßmus 		int32 lastIndex = document->CountParagraphs() - 1;
527f890fab6SStephan Aßmus 		if (lastIndex > 0) {
528f890fab6SStephan Aßmus 			const Paragraph& otherParagraph = document->ParagraphAt(lastIndex);
529f890fab6SStephan Aßmus 			if (otherParagraph.EndsWith("\n")) {
530f890fab6SStephan Aßmus 				// TODO: Import/map CharacterStyles and ParagraphStyle
531f890fab6SStephan Aßmus 				if (!fParagraphs.Add(otherParagraph, ++index))
532f890fab6SStephan Aßmus 					return B_NO_MEMORY;
533f890fab6SStephan Aßmus 			} else {
534f890fab6SStephan Aßmus 				const TextSpanList& textSpans = otherParagraph.TextSpans();
535f890fab6SStephan Aßmus 				int32 spanCount = textSpans.CountItems();
536f890fab6SStephan Aßmus 				for (int32 i = 0; i < spanCount; i++) {
537f890fab6SStephan Aßmus 					const TextSpan& span = textSpans.ItemAtFast(i);
538f890fab6SStephan Aßmus 					// TODO: Import/map CharacterStyles
539f890fab6SStephan Aßmus 					if (!paragraph2.Prepend(span))
540f890fab6SStephan Aßmus 						return B_NO_MEMORY;
541f890fab6SStephan Aßmus 				}
542f890fab6SStephan Aßmus 			}
543f890fab6SStephan Aßmus 		}
544f890fab6SStephan Aßmus 
545f890fab6SStephan Aßmus 		// Insert back the second paragraph-part
5464a96bcdaSStephan Aßmus 		if (paragraph2.IsEmpty()) {
5474a96bcdaSStephan Aßmus 			// Make sure Paragraph has at least one TextSpan, even
548*5f80d48aSStephan Aßmus 			// if its empty. This handles the case of inserting a
549*5f80d48aSStephan Aßmus 			// line-break at the end of the document. It than needs to
550*5f80d48aSStephan Aßmus 			// have a new, empty paragraph at the end.
5514a96bcdaSStephan Aßmus 			const TextSpanList& spans = paragraph1.TextSpans();
5524a96bcdaSStephan Aßmus 			const TextSpan& span = spans.LastItem();
553f890fab6SStephan Aßmus 			if (!paragraph2.Append(TextSpan("", span.Style())))
554f890fab6SStephan Aßmus 				return B_NO_MEMORY;
5554a96bcdaSStephan Aßmus 		}
5564a96bcdaSStephan Aßmus 
557f890fab6SStephan Aßmus 		if (!fParagraphs.Add(paragraph2, ++index))
5584a96bcdaSStephan Aßmus 			return B_NO_MEMORY;
559f890fab6SStephan Aßmus 
5604a96bcdaSStephan Aßmus 		paragraphCount++;
5614a96bcdaSStephan Aßmus 	} else {
5624a96bcdaSStephan Aßmus 		Paragraph paragraph(ParagraphAt(index));
563f890fab6SStephan Aßmus 		const Paragraph& otherParagraph = document->ParagraphAt(0);
564f890fab6SStephan Aßmus 
565f890fab6SStephan Aßmus 		const TextSpanList& textSpans = otherParagraph.TextSpans();
566f890fab6SStephan Aßmus 		int32 spanCount = textSpans.CountItems();
567f890fab6SStephan Aßmus 		for (int32 i = 0; i < spanCount; i++) {
568f890fab6SStephan Aßmus 			const TextSpan& span = textSpans.ItemAtFast(i);
569f890fab6SStephan Aßmus 			paragraph.Insert(textOffset, span);
570f890fab6SStephan Aßmus 			textOffset += span.CountChars();
571f890fab6SStephan Aßmus 		}
572f890fab6SStephan Aßmus 
5734a96bcdaSStephan Aßmus 		if (!fParagraphs.Replace(index, paragraph))
5744a96bcdaSStephan Aßmus 			return B_NO_MEMORY;
575f890fab6SStephan Aßmus 
5764a96bcdaSStephan Aßmus 		paragraphCount++;
5774a96bcdaSStephan Aßmus 	}
5784a96bcdaSStephan Aßmus 
5794a96bcdaSStephan Aßmus 	return B_OK;
5804a96bcdaSStephan Aßmus }
5814a96bcdaSStephan Aßmus 
5824a96bcdaSStephan Aßmus 
5834a96bcdaSStephan Aßmus status_t
5844a96bcdaSStephan Aßmus TextDocument::_Remove(int32 textOffset, int32 length, int32& index,
5854a96bcdaSStephan Aßmus 	int32& paragraphCount)
5864a96bcdaSStephan Aßmus {
5874a96bcdaSStephan Aßmus 	if (length == 0)
5884a96bcdaSStephan Aßmus 		return B_OK;
5894a96bcdaSStephan Aßmus 
5904a96bcdaSStephan Aßmus 	int32 paragraphOffset;
5914a96bcdaSStephan Aßmus 	index = ParagraphIndexFor(textOffset, paragraphOffset);
5924a96bcdaSStephan Aßmus 	if (index < 0)
5934a96bcdaSStephan Aßmus 		return B_BAD_VALUE;
5944a96bcdaSStephan Aßmus 
5954a96bcdaSStephan Aßmus 	textOffset -= paragraphOffset;
5964a96bcdaSStephan Aßmus 	paragraphCount++;
5974a96bcdaSStephan Aßmus 
5984a96bcdaSStephan Aßmus 	// The paragraph at the text offset remains, even if the offset is at
5994a96bcdaSStephan Aßmus 	// the beginning of that paragraph. The idea is that the selection start
6004a96bcdaSStephan Aßmus 	// stays visually in the same place. Therefore, the paragraph at that
6014a96bcdaSStephan Aßmus 	// offset has to keep the paragraph style from that paragraph.
6024a96bcdaSStephan Aßmus 
6034a96bcdaSStephan Aßmus 	Paragraph resultParagraph(ParagraphAt(index));
6044a96bcdaSStephan Aßmus 	int32 paragraphLength = resultParagraph.Length();
6054a96bcdaSStephan Aßmus 	if (textOffset == 0 && length > paragraphLength) {
6064a96bcdaSStephan Aßmus 		length -= paragraphLength;
6074a96bcdaSStephan Aßmus 		paragraphLength = 0;
6084a96bcdaSStephan Aßmus 		resultParagraph.Clear();
6094a96bcdaSStephan Aßmus 	} else {
6104a96bcdaSStephan Aßmus 		int32 removeLength = std::min(length, paragraphLength - textOffset);
6114a96bcdaSStephan Aßmus 		resultParagraph.Remove(textOffset, removeLength);
6124a96bcdaSStephan Aßmus 		paragraphLength -= removeLength;
6134a96bcdaSStephan Aßmus 		length -= removeLength;
6144a96bcdaSStephan Aßmus 	}
6154a96bcdaSStephan Aßmus 
6164a96bcdaSStephan Aßmus 	if (textOffset == paragraphLength && length == 0
6174a96bcdaSStephan Aßmus 		&& index + 1 < fParagraphs.CountItems()) {
6184a96bcdaSStephan Aßmus 		// Line break between paragraphs got removed. Shift the next
6194a96bcdaSStephan Aßmus 		// paragraph's text spans into the resulting one.
6204a96bcdaSStephan Aßmus 
6214a96bcdaSStephan Aßmus 		const TextSpanList&	textSpans = ParagraphAt(index + 1).TextSpans();
6224a96bcdaSStephan Aßmus 		int32 spanCount = textSpans.CountItems();
6234a96bcdaSStephan Aßmus 		for (int32 i = 0; i < spanCount; i++) {
6244a96bcdaSStephan Aßmus 			const TextSpan& span = textSpans.ItemAtFast(i);
6254a96bcdaSStephan Aßmus 			resultParagraph.Append(span);
6264a96bcdaSStephan Aßmus 		}
6274a96bcdaSStephan Aßmus 		fParagraphs.Remove(index + 1);
6284a96bcdaSStephan Aßmus 		paragraphCount++;
6294a96bcdaSStephan Aßmus 	}
6304a96bcdaSStephan Aßmus 
6314a96bcdaSStephan Aßmus 	textOffset = 0;
6324a96bcdaSStephan Aßmus 
6334a96bcdaSStephan Aßmus 	while (length > 0 && index + 1 < fParagraphs.CountItems()) {
6344a96bcdaSStephan Aßmus 		paragraphCount++;
6354a96bcdaSStephan Aßmus 		const Paragraph& paragraph = ParagraphAt(index + 1);
6364a96bcdaSStephan Aßmus 		paragraphLength = paragraph.Length();
6374a96bcdaSStephan Aßmus 		// Remove paragraph in any case. If some of it remains, the last
6384a96bcdaSStephan Aßmus 		// paragraph to remove is reached, and the remaining spans are
6394a96bcdaSStephan Aßmus 		// transfered to the result parahraph.
6404a96bcdaSStephan Aßmus 		if (length >= paragraphLength) {
6414a96bcdaSStephan Aßmus 			length -= paragraphLength;
6424a96bcdaSStephan Aßmus 			fParagraphs.Remove(index);
6434a96bcdaSStephan Aßmus 		} else {
6444a96bcdaSStephan Aßmus 			// Last paragraph reached
6454a96bcdaSStephan Aßmus 			int32 removedLength = std::min(length, paragraphLength);
6464a96bcdaSStephan Aßmus 			Paragraph newParagraph(paragraph);
6474a96bcdaSStephan Aßmus 			fParagraphs.Remove(index + 1);
6484a96bcdaSStephan Aßmus 
6494a96bcdaSStephan Aßmus 			if (!newParagraph.Remove(0, removedLength))
6504a96bcdaSStephan Aßmus 				return B_NO_MEMORY;
6514a96bcdaSStephan Aßmus 
6524a96bcdaSStephan Aßmus 			// Transfer remaining spans to resultParagraph
6534a96bcdaSStephan Aßmus 			const TextSpanList&	textSpans = newParagraph.TextSpans();
6544a96bcdaSStephan Aßmus 			int32 spanCount = textSpans.CountItems();
6554a96bcdaSStephan Aßmus 			for (int32 i = 0; i < spanCount; i++) {
6564a96bcdaSStephan Aßmus 				const TextSpan& span = textSpans.ItemAtFast(i);
6574a96bcdaSStephan Aßmus 				resultParagraph.Append(span);
6584a96bcdaSStephan Aßmus 			}
6594a96bcdaSStephan Aßmus 
6604a96bcdaSStephan Aßmus 			break;
6614a96bcdaSStephan Aßmus 		}
6624a96bcdaSStephan Aßmus 	}
6634a96bcdaSStephan Aßmus 
6644a96bcdaSStephan Aßmus 	fParagraphs.Replace(index, resultParagraph);
6654a96bcdaSStephan Aßmus 
6664a96bcdaSStephan Aßmus 	return B_OK;
6674a96bcdaSStephan Aßmus }
6684a96bcdaSStephan Aßmus 
6694a96bcdaSStephan Aßmus 
6704a96bcdaSStephan Aßmus // #pragma mark - notifications
6714a96bcdaSStephan Aßmus 
6724a96bcdaSStephan Aßmus 
673d7f7bf2dSAxel Dörfler void
674d7f7bf2dSAxel Dörfler TextDocument::_NotifyTextChanging(TextChangingEvent& event) const
675d7f7bf2dSAxel Dörfler {
676d7f7bf2dSAxel Dörfler 	// Copy listener list to have a stable list in case listeners
677d7f7bf2dSAxel Dörfler 	// are added/removed from within the notification hook.
678d7f7bf2dSAxel Dörfler 	TextListenerList listeners(fTextListeners);
679d7f7bf2dSAxel Dörfler 	int32 count = listeners.CountItems();
680d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
681d7f7bf2dSAxel Dörfler 		const TextListenerRef& listener = listeners.ItemAtFast(i);
682d7f7bf2dSAxel Dörfler 		if (listener.Get() == NULL)
683d7f7bf2dSAxel Dörfler 			continue;
684d7f7bf2dSAxel Dörfler 		listener->TextChanging(event);
685d7f7bf2dSAxel Dörfler 		if (event.IsCanceled())
686d7f7bf2dSAxel Dörfler 			break;
687d7f7bf2dSAxel Dörfler 	}
688d7f7bf2dSAxel Dörfler }
689d7f7bf2dSAxel Dörfler 
690d7f7bf2dSAxel Dörfler 
691d7f7bf2dSAxel Dörfler void
692d7f7bf2dSAxel Dörfler TextDocument::_NotifyTextChanged(const TextChangedEvent& event) const
693d7f7bf2dSAxel Dörfler {
694d7f7bf2dSAxel Dörfler 	// Copy listener list to have a stable list in case listeners
695d7f7bf2dSAxel Dörfler 	// are added/removed from within the notification hook.
696d7f7bf2dSAxel Dörfler 	TextListenerList listeners(fTextListeners);
697d7f7bf2dSAxel Dörfler 	int32 count = listeners.CountItems();
698d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
699d7f7bf2dSAxel Dörfler 		const TextListenerRef& listener = listeners.ItemAtFast(i);
700d7f7bf2dSAxel Dörfler 		if (listener.Get() == NULL)
701d7f7bf2dSAxel Dörfler 			continue;
702d7f7bf2dSAxel Dörfler 		listener->TextChanged(event);
703d7f7bf2dSAxel Dörfler 	}
704d7f7bf2dSAxel Dörfler }
705d7f7bf2dSAxel Dörfler 
7064bf45bfbSStephan Aßmus 
7074bf45bfbSStephan Aßmus void
7084bf45bfbSStephan Aßmus TextDocument::_NotifyUndoableEditHappened(const UndoableEditRef& edit) const
7094bf45bfbSStephan Aßmus {
7104bf45bfbSStephan Aßmus 	// Copy listener list to have a stable list in case listeners
7114bf45bfbSStephan Aßmus 	// are added/removed from within the notification hook.
7124bf45bfbSStephan Aßmus 	UndoListenerList listeners(fUndoListeners);
7134bf45bfbSStephan Aßmus 	int32 count = listeners.CountItems();
7144bf45bfbSStephan Aßmus 	for (int32 i = 0; i < count; i++) {
7154bf45bfbSStephan Aßmus 		const UndoableEditListenerRef& listener = listeners.ItemAtFast(i);
7164bf45bfbSStephan Aßmus 		if (listener.Get() == NULL)
7174bf45bfbSStephan Aßmus 			continue;
7184bf45bfbSStephan Aßmus 		listener->UndoableEditHappened(this, edit);
7194bf45bfbSStephan Aßmus 	}
7204bf45bfbSStephan Aßmus }
721