xref: /haiku/src/apps/haikudepot/textview/TextDocument.cpp (revision 3d2fd2acaf1ed103639675b3116c2ac874aa174d)
1d7f7bf2dSAxel Dörfler /*
2d7f7bf2dSAxel Dörfler  * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
3*3d2fd2acSAndrew 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>
11*3d2fd2acSAndrew 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;
169*3d2fd2acSAndrew Lindesay 	int32 index;
170*3d2fd2acSAndrew Lindesay 	int32 count = paragraph.CountTextSpans();
171d7f7bf2dSAxel Dörfler 
172*3d2fd2acSAndrew Lindesay 	for (index = 0; index < count; index++) {
173*3d2fd2acSAndrew 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 {
197f890fab6SStephan Aßmus 	return fParagraphs.CountItems();
198f890fab6SStephan Aßmus }
199f890fab6SStephan Aßmus 
200f890fab6SStephan Aßmus 
201f890fab6SStephan Aßmus int32
202d7f7bf2dSAxel Dörfler TextDocument::ParagraphIndexFor(int32 textOffset, int32& paragraphOffset) const
203d7f7bf2dSAxel Dörfler {
204d7f7bf2dSAxel Dörfler 	// TODO: Could binary search the Paragraphs if they were wrapped in classes
205d7f7bf2dSAxel Dörfler 	// that knew there text offset in the document.
206d7f7bf2dSAxel Dörfler 	int32 textLength = 0;
207d7f7bf2dSAxel Dörfler 	paragraphOffset = 0;
208d7f7bf2dSAxel Dörfler 	int32 count = fParagraphs.CountItems();
209d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
210d7f7bf2dSAxel Dörfler 		const Paragraph& paragraph = fParagraphs.ItemAtFast(i);
211d7f7bf2dSAxel Dörfler 		int32 paragraphLength = paragraph.Length();
212d7f7bf2dSAxel Dörfler 		textLength += paragraphLength;
213d7f7bf2dSAxel Dörfler 		if (textLength > textOffset
214d7f7bf2dSAxel Dörfler 			|| (i == count - 1 && textLength == textOffset)) {
215d7f7bf2dSAxel Dörfler 			return i;
216d7f7bf2dSAxel Dörfler 		}
217d7f7bf2dSAxel Dörfler 		paragraphOffset += paragraphLength;
218d7f7bf2dSAxel Dörfler 	}
219d7f7bf2dSAxel Dörfler 	return -1;
220d7f7bf2dSAxel Dörfler }
221d7f7bf2dSAxel Dörfler 
222d7f7bf2dSAxel Dörfler 
223d7f7bf2dSAxel Dörfler const Paragraph&
224d7f7bf2dSAxel Dörfler TextDocument::ParagraphAt(int32 textOffset, int32& paragraphOffset) const
225d7f7bf2dSAxel Dörfler {
226d7f7bf2dSAxel Dörfler 	int32 index = ParagraphIndexFor(textOffset, paragraphOffset);
227d7f7bf2dSAxel Dörfler 	if (index >= 0)
228d7f7bf2dSAxel Dörfler 		return fParagraphs.ItemAtFast(index);
229d7f7bf2dSAxel Dörfler 
230d7f7bf2dSAxel Dörfler 	return fEmptyLastParagraph;
231d7f7bf2dSAxel Dörfler }
232d7f7bf2dSAxel Dörfler 
233d7f7bf2dSAxel Dörfler 
234d7f7bf2dSAxel Dörfler const Paragraph&
235d7f7bf2dSAxel Dörfler TextDocument::ParagraphAt(int32 index) const
236d7f7bf2dSAxel Dörfler {
237d7f7bf2dSAxel Dörfler 	if (index >= 0 && index < fParagraphs.CountItems())
238d7f7bf2dSAxel Dörfler 		return fParagraphs.ItemAtFast(index);
239d7f7bf2dSAxel Dörfler 	return fEmptyLastParagraph;
240d7f7bf2dSAxel Dörfler }
241d7f7bf2dSAxel Dörfler 
242d7f7bf2dSAxel Dörfler 
243d7f7bf2dSAxel Dörfler bool
244d7f7bf2dSAxel Dörfler TextDocument::Append(const Paragraph& paragraph)
245d7f7bf2dSAxel Dörfler {
246d7f7bf2dSAxel Dörfler 	return fParagraphs.Add(paragraph);
247d7f7bf2dSAxel Dörfler }
248d7f7bf2dSAxel Dörfler 
249d7f7bf2dSAxel Dörfler 
250d7f7bf2dSAxel Dörfler int32
251d7f7bf2dSAxel Dörfler TextDocument::Length() const
252d7f7bf2dSAxel Dörfler {
253d7f7bf2dSAxel Dörfler 	// TODO: Could be O(1) if the Paragraphs were wrapped in classes that
2544a96bcdaSStephan Aßmus 	// knew their text offset in the document.
255d7f7bf2dSAxel Dörfler 	int32 textLength = 0;
256d7f7bf2dSAxel Dörfler 	int32 count = fParagraphs.CountItems();
257d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
258d7f7bf2dSAxel Dörfler 		const Paragraph& paragraph = fParagraphs.ItemAtFast(i);
259d7f7bf2dSAxel Dörfler 		textLength += paragraph.Length();
260d7f7bf2dSAxel Dörfler 	}
261d7f7bf2dSAxel Dörfler 	return textLength;
262d7f7bf2dSAxel Dörfler }
263d7f7bf2dSAxel Dörfler 
264d7f7bf2dSAxel Dörfler 
265d7f7bf2dSAxel Dörfler BString
266d7f7bf2dSAxel Dörfler TextDocument::Text() const
267d7f7bf2dSAxel Dörfler {
268d7f7bf2dSAxel Dörfler 	return Text(0, Length());
269d7f7bf2dSAxel Dörfler }
270d7f7bf2dSAxel Dörfler 
271d7f7bf2dSAxel Dörfler 
272d7f7bf2dSAxel Dörfler BString
273d7f7bf2dSAxel Dörfler TextDocument::Text(int32 start, int32 length) const
274d7f7bf2dSAxel Dörfler {
275d7f7bf2dSAxel Dörfler 	if (start < 0)
276d7f7bf2dSAxel Dörfler 		start = 0;
277d7f7bf2dSAxel Dörfler 
278d7f7bf2dSAxel Dörfler 	BString text;
279d7f7bf2dSAxel Dörfler 
280d7f7bf2dSAxel Dörfler 	int32 count = fParagraphs.CountItems();
281d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
282d7f7bf2dSAxel Dörfler 		const Paragraph& paragraph = fParagraphs.ItemAtFast(i);
283d7f7bf2dSAxel Dörfler 		int32 paragraphLength = paragraph.Length();
284d7f7bf2dSAxel Dörfler 		if (paragraphLength == 0)
285d7f7bf2dSAxel Dörfler 			continue;
286d7f7bf2dSAxel Dörfler 		if (start > paragraphLength) {
287d7f7bf2dSAxel Dörfler 			// Skip paragraph if its before start
288d7f7bf2dSAxel Dörfler 			start -= paragraphLength;
289d7f7bf2dSAxel Dörfler 			continue;
290d7f7bf2dSAxel Dörfler 		}
291d7f7bf2dSAxel Dörfler 
292d7f7bf2dSAxel Dörfler 		// Remaining paragraph length after start
293d7f7bf2dSAxel Dörfler 		paragraphLength -= start;
294d7f7bf2dSAxel Dörfler 		int32 copyLength = std::min(paragraphLength, length);
295d7f7bf2dSAxel Dörfler 
296d7f7bf2dSAxel Dörfler 		text << paragraph.Text(start, copyLength);
297d7f7bf2dSAxel Dörfler 
298d7f7bf2dSAxel Dörfler 		length -= copyLength;
299d7f7bf2dSAxel Dörfler 		if (length == 0)
300d7f7bf2dSAxel Dörfler 			break;
301d7f7bf2dSAxel Dörfler 
302d7f7bf2dSAxel Dörfler 		// Next paragraph is copied from its beginning
303d7f7bf2dSAxel Dörfler 		start = 0;
304d7f7bf2dSAxel Dörfler 	}
305d7f7bf2dSAxel Dörfler 
306d7f7bf2dSAxel Dörfler 	return text;
307d7f7bf2dSAxel Dörfler }
308d7f7bf2dSAxel Dörfler 
309d7f7bf2dSAxel Dörfler 
310d7f7bf2dSAxel Dörfler TextDocumentRef
311d7f7bf2dSAxel Dörfler TextDocument::SubDocument(int32 start, int32 length) const
312d7f7bf2dSAxel Dörfler {
313d7f7bf2dSAxel Dörfler 	TextDocumentRef result(new(std::nothrow) TextDocument(
314d7f7bf2dSAxel Dörfler 		fDefaultCharacterStyle, fEmptyLastParagraph.Style()), true);
315d7f7bf2dSAxel Dörfler 
316779ab335SX512 	if (!result.IsSet())
317d7f7bf2dSAxel Dörfler 		return result;
318d7f7bf2dSAxel Dörfler 
319d7f7bf2dSAxel Dörfler 	if (start < 0)
320d7f7bf2dSAxel Dörfler 		start = 0;
321d7f7bf2dSAxel Dörfler 
322d7f7bf2dSAxel Dörfler 	int32 count = fParagraphs.CountItems();
323d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
324d7f7bf2dSAxel Dörfler 		const Paragraph& paragraph = fParagraphs.ItemAtFast(i);
325d7f7bf2dSAxel Dörfler 		int32 paragraphLength = paragraph.Length();
326d7f7bf2dSAxel Dörfler 		if (paragraphLength == 0)
327d7f7bf2dSAxel Dörfler 			continue;
328d7f7bf2dSAxel Dörfler 		if (start > paragraphLength) {
329d7f7bf2dSAxel Dörfler 			// Skip paragraph if its before start
330d7f7bf2dSAxel Dörfler 			start -= paragraphLength;
331d7f7bf2dSAxel Dörfler 			continue;
332d7f7bf2dSAxel Dörfler 		}
333d7f7bf2dSAxel Dörfler 
334d7f7bf2dSAxel Dörfler 		// Remaining paragraph length after start
335d7f7bf2dSAxel Dörfler 		paragraphLength -= start;
336d7f7bf2dSAxel Dörfler 		int32 copyLength = std::min(paragraphLength, length);
337d7f7bf2dSAxel Dörfler 
338d7f7bf2dSAxel Dörfler 		result->Append(paragraph.SubParagraph(start, copyLength));
339d7f7bf2dSAxel Dörfler 
340d7f7bf2dSAxel Dörfler 		length -= copyLength;
341d7f7bf2dSAxel Dörfler 		if (length == 0)
342d7f7bf2dSAxel Dörfler 			break;
343d7f7bf2dSAxel Dörfler 
344d7f7bf2dSAxel Dörfler 		// Next paragraph is copied from its beginning
345d7f7bf2dSAxel Dörfler 		start = 0;
346d7f7bf2dSAxel Dörfler 	}
347d7f7bf2dSAxel Dörfler 
348d7f7bf2dSAxel Dörfler 	return result;
349d7f7bf2dSAxel Dörfler }
350d7f7bf2dSAxel Dörfler 
351d7f7bf2dSAxel Dörfler 
352d7f7bf2dSAxel Dörfler // #pragma mark -
353d7f7bf2dSAxel Dörfler 
354d7f7bf2dSAxel Dörfler 
355d7f7bf2dSAxel Dörfler void
356d7f7bf2dSAxel Dörfler TextDocument::PrintToStream() const
357d7f7bf2dSAxel Dörfler {
358d7f7bf2dSAxel Dörfler 	int32 paragraphCount = fParagraphs.CountItems();
359d7f7bf2dSAxel Dörfler 	if (paragraphCount == 0) {
360d7f7bf2dSAxel Dörfler 		printf("<document/>\n");
361d7f7bf2dSAxel Dörfler 		return;
362d7f7bf2dSAxel Dörfler 	}
363d7f7bf2dSAxel Dörfler 	printf("<document>\n");
364d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < paragraphCount; i++) {
365d7f7bf2dSAxel Dörfler 		fParagraphs.ItemAtFast(i).PrintToStream();
366d7f7bf2dSAxel Dörfler 	}
367d7f7bf2dSAxel Dörfler 	printf("</document>\n");
368d7f7bf2dSAxel Dörfler }
369d7f7bf2dSAxel Dörfler 
370d7f7bf2dSAxel Dörfler 
371f890fab6SStephan Aßmus /*static*/ TextDocumentRef
372f890fab6SStephan Aßmus TextDocument::NormalizeText(const BString& text,
373f890fab6SStephan Aßmus 	CharacterStyle characterStyle, ParagraphStyle paragraphStyle)
374d7f7bf2dSAxel Dörfler {
375f890fab6SStephan Aßmus 	TextDocumentRef document(new(std::nothrow) TextDocument(characterStyle,
376f890fab6SStephan Aßmus 			paragraphStyle), true);
377779ab335SX512 	if (!document.IsSet())
378f890fab6SStephan Aßmus 		throw B_NO_MEMORY;
379d7f7bf2dSAxel Dörfler 
380f890fab6SStephan Aßmus 	Paragraph paragraph(paragraphStyle);
381d7f7bf2dSAxel Dörfler 
382f890fab6SStephan Aßmus 	// Append TextSpans, splitting 'text' into Paragraphs at line breaks.
3834a96bcdaSStephan Aßmus 	int32 length = text.CountChars();
3844a96bcdaSStephan Aßmus 	int32 chunkStart = 0;
3854a96bcdaSStephan Aßmus 	while (chunkStart < length) {
3864a96bcdaSStephan Aßmus 		int32 chunkEnd = text.FindFirst('\n', chunkStart);
3874a96bcdaSStephan Aßmus 		bool foundLineBreak = chunkEnd >= chunkStart;
3884a96bcdaSStephan Aßmus 		if (foundLineBreak)
3894a96bcdaSStephan Aßmus 			chunkEnd++;
3904a96bcdaSStephan Aßmus 		else
3914a96bcdaSStephan Aßmus 			chunkEnd = length;
3924a96bcdaSStephan Aßmus 
3934a96bcdaSStephan Aßmus 		BString chunk;
3944a96bcdaSStephan Aßmus 		text.CopyCharsInto(chunk, chunkStart, chunkEnd - chunkStart);
3954a96bcdaSStephan Aßmus 		TextSpan span(chunk, characterStyle);
3964a96bcdaSStephan Aßmus 
397f890fab6SStephan Aßmus 		if (!paragraph.Append(span))
398f890fab6SStephan Aßmus 			throw B_NO_MEMORY;
399f890fab6SStephan Aßmus 		if (paragraph.Length() > 0 && !document->Append(paragraph))
400f890fab6SStephan Aßmus 			throw B_NO_MEMORY;
4014a96bcdaSStephan Aßmus 
402f890fab6SStephan Aßmus 		paragraph = Paragraph(paragraphStyle);
4034a96bcdaSStephan Aßmus 		chunkStart = chunkEnd + 1;
4044a96bcdaSStephan Aßmus 	}
4054a96bcdaSStephan Aßmus 
406f890fab6SStephan Aßmus 	return document;
407f890fab6SStephan Aßmus }
408f890fab6SStephan Aßmus 
409f890fab6SStephan Aßmus 
410f890fab6SStephan Aßmus // #pragma mark -
411f890fab6SStephan Aßmus 
412f890fab6SStephan Aßmus 
413f890fab6SStephan Aßmus bool
414f890fab6SStephan Aßmus TextDocument::AddListener(TextListenerRef listener)
415f890fab6SStephan Aßmus {
416f890fab6SStephan Aßmus 	return fTextListeners.Add(listener);
417f890fab6SStephan Aßmus }
418f890fab6SStephan Aßmus 
419f890fab6SStephan Aßmus 
420f890fab6SStephan Aßmus bool
421f890fab6SStephan Aßmus TextDocument::RemoveListener(TextListenerRef listener)
422f890fab6SStephan Aßmus {
423f890fab6SStephan Aßmus 	return fTextListeners.Remove(listener);
424f890fab6SStephan Aßmus }
425f890fab6SStephan Aßmus 
426f890fab6SStephan Aßmus 
427f890fab6SStephan Aßmus bool
428f890fab6SStephan Aßmus TextDocument::AddUndoListener(UndoableEditListenerRef listener)
429f890fab6SStephan Aßmus {
430f890fab6SStephan Aßmus 	return fUndoListeners.Add(listener);
431f890fab6SStephan Aßmus }
432f890fab6SStephan Aßmus 
433f890fab6SStephan Aßmus 
434f890fab6SStephan Aßmus bool
435f890fab6SStephan Aßmus TextDocument::RemoveUndoListener(UndoableEditListenerRef listener)
436f890fab6SStephan Aßmus {
437f890fab6SStephan Aßmus 	return fUndoListeners.Remove(listener);
438f890fab6SStephan Aßmus }
439f890fab6SStephan Aßmus 
440f890fab6SStephan Aßmus 
441f890fab6SStephan Aßmus // #pragma mark - private
442f890fab6SStephan Aßmus 
443f890fab6SStephan Aßmus 
444f890fab6SStephan Aßmus status_t
445f890fab6SStephan Aßmus TextDocument::_Insert(int32 textOffset, TextDocumentRef document,
446f890fab6SStephan Aßmus 	int32& index, int32& paragraphCount)
447f890fab6SStephan Aßmus {
448f890fab6SStephan Aßmus 	int32 paragraphOffset;
449f890fab6SStephan Aßmus 	index = ParagraphIndexFor(textOffset, paragraphOffset);
450f890fab6SStephan Aßmus 	if (index < 0)
451f890fab6SStephan Aßmus 		return B_BAD_VALUE;
452f890fab6SStephan Aßmus 
453f890fab6SStephan Aßmus 	if (document->Length() == 0)
454f890fab6SStephan Aßmus 		return B_OK;
455f890fab6SStephan Aßmus 
456f890fab6SStephan Aßmus 	textOffset -= paragraphOffset;
457f890fab6SStephan Aßmus 
458f890fab6SStephan Aßmus 	bool hasLineBreaks;
459f890fab6SStephan Aßmus 	if (document->CountParagraphs() > 1) {
460f890fab6SStephan Aßmus 		hasLineBreaks = true;
461f890fab6SStephan Aßmus 	} else {
462f890fab6SStephan Aßmus 		const Paragraph& paragraph = document->ParagraphAt(0);
463f890fab6SStephan Aßmus 		hasLineBreaks = paragraph.EndsWith("\n");
464f890fab6SStephan Aßmus 	}
465f890fab6SStephan Aßmus 
466f890fab6SStephan Aßmus 	if (hasLineBreaks) {
467f890fab6SStephan Aßmus 		// Split paragraph at textOffset
468f890fab6SStephan Aßmus 		Paragraph paragraph1(ParagraphAt(index).Style());
469f890fab6SStephan Aßmus 		Paragraph paragraph2(document->ParagraphAt(
470f890fab6SStephan Aßmus 			document->CountParagraphs() - 1).Style());
471f890fab6SStephan Aßmus 		{
472*3d2fd2acSAndrew Lindesay 			const Paragraph& paragraphAtIndex = ParagraphAt(index);
473*3d2fd2acSAndrew Lindesay 			int32 spanCount = paragraphAtIndex.CountTextSpans();
474f890fab6SStephan Aßmus 			for (int32 i = 0; i < spanCount; i++) {
475*3d2fd2acSAndrew Lindesay 				const TextSpan& span = paragraphAtIndex.TextSpanAtIndex(i);
476f890fab6SStephan Aßmus 				int32 spanLength = span.CountChars();
477f890fab6SStephan Aßmus 				if (textOffset >= spanLength) {
478f890fab6SStephan Aßmus 					if (!paragraph1.Append(span))
479f890fab6SStephan Aßmus 						return B_NO_MEMORY;
480f890fab6SStephan Aßmus 					textOffset -= spanLength;
481f890fab6SStephan Aßmus 				} else if (textOffset > 0) {
482f890fab6SStephan Aßmus 					if (!paragraph1.Append(
483f890fab6SStephan Aßmus 							span.SubSpan(0, textOffset))
484f890fab6SStephan Aßmus 						|| !paragraph2.Append(
485f890fab6SStephan Aßmus 							span.SubSpan(textOffset,
486f890fab6SStephan Aßmus 								spanLength - textOffset))) {
487f890fab6SStephan Aßmus 						return B_NO_MEMORY;
488f890fab6SStephan Aßmus 					}
489f890fab6SStephan Aßmus 					textOffset = 0;
490f890fab6SStephan Aßmus 				} else {
491f890fab6SStephan Aßmus 					if (!paragraph2.Append(span))
492f890fab6SStephan Aßmus 						return B_NO_MEMORY;
493f890fab6SStephan Aßmus 				}
494f890fab6SStephan Aßmus 			}
495f890fab6SStephan Aßmus 		}
496f890fab6SStephan Aßmus 
497f890fab6SStephan Aßmus 		fParagraphs.Remove(index);
498f890fab6SStephan Aßmus 
499f890fab6SStephan Aßmus 		// Append first paragraph in other document to first part of
500f890fab6SStephan Aßmus 		// paragraph at insert position
501f890fab6SStephan Aßmus 		{
502f890fab6SStephan Aßmus 			const Paragraph& otherParagraph = document->ParagraphAt(0);
503*3d2fd2acSAndrew Lindesay 			int32 spanCount = otherParagraph.CountTextSpans();
504f890fab6SStephan Aßmus 			for (int32 i = 0; i < spanCount; i++) {
505*3d2fd2acSAndrew Lindesay 				const TextSpan& span = otherParagraph.TextSpanAtIndex(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 {
534*3d2fd2acSAndrew Lindesay 				int32 spanCount = otherParagraph.CountTextSpans();
535f890fab6SStephan Aßmus 				for (int32 i = 0; i < spanCount; i++) {
536*3d2fd2acSAndrew Lindesay 					const TextSpan& span = otherParagraph.TextSpanAtIndex(i);
537f890fab6SStephan Aßmus 					// TODO: Import/map CharacterStyles
538f890fab6SStephan Aßmus 					if (!paragraph2.Prepend(span))
539f890fab6SStephan Aßmus 						return B_NO_MEMORY;
540f890fab6SStephan Aßmus 				}
541f890fab6SStephan Aßmus 			}
542f890fab6SStephan Aßmus 		}
543f890fab6SStephan Aßmus 
544f890fab6SStephan Aßmus 		// Insert back the second paragraph-part
5454a96bcdaSStephan Aßmus 		if (paragraph2.IsEmpty()) {
5464a96bcdaSStephan Aßmus 			// Make sure Paragraph has at least one TextSpan, even
5475f80d48aSStephan Aßmus 			// if its empty. This handles the case of inserting a
5485f80d48aSStephan Aßmus 			// line-break at the end of the document. It than needs to
5495f80d48aSStephan Aßmus 			// have a new, empty paragraph at the end.
550*3d2fd2acSAndrew Lindesay 			const int32 indexLastSpan = paragraph1.CountTextSpans() - 1;
551*3d2fd2acSAndrew Lindesay 			const TextSpan& span = paragraph1.TextSpanAtIndex(indexLastSpan);
552f890fab6SStephan Aßmus 			if (!paragraph2.Append(TextSpan("", span.Style())))
553f890fab6SStephan Aßmus 				return B_NO_MEMORY;
5544a96bcdaSStephan Aßmus 		}
5554a96bcdaSStephan Aßmus 
556f890fab6SStephan Aßmus 		if (!fParagraphs.Add(paragraph2, ++index))
5574a96bcdaSStephan Aßmus 			return B_NO_MEMORY;
558f890fab6SStephan Aßmus 
5594a96bcdaSStephan Aßmus 		paragraphCount++;
5604a96bcdaSStephan Aßmus 	} else {
5614a96bcdaSStephan Aßmus 		Paragraph paragraph(ParagraphAt(index));
562f890fab6SStephan Aßmus 		const Paragraph& otherParagraph = document->ParagraphAt(0);
563f890fab6SStephan Aßmus 
564*3d2fd2acSAndrew Lindesay 		int32 spanCount = otherParagraph.CountTextSpans();
565f890fab6SStephan Aßmus 		for (int32 i = 0; i < spanCount; i++) {
566*3d2fd2acSAndrew Lindesay 			const TextSpan& span = otherParagraph.TextSpanAtIndex(i);
567f890fab6SStephan Aßmus 			paragraph.Insert(textOffset, span);
568f890fab6SStephan Aßmus 			textOffset += span.CountChars();
569f890fab6SStephan Aßmus 		}
570f890fab6SStephan Aßmus 
5714a96bcdaSStephan Aßmus 		if (!fParagraphs.Replace(index, paragraph))
5724a96bcdaSStephan Aßmus 			return B_NO_MEMORY;
573f890fab6SStephan Aßmus 
5744a96bcdaSStephan Aßmus 		paragraphCount++;
5754a96bcdaSStephan Aßmus 	}
5764a96bcdaSStephan Aßmus 
5774a96bcdaSStephan Aßmus 	return B_OK;
5784a96bcdaSStephan Aßmus }
5794a96bcdaSStephan Aßmus 
5804a96bcdaSStephan Aßmus 
5814a96bcdaSStephan Aßmus status_t
5824a96bcdaSStephan Aßmus TextDocument::_Remove(int32 textOffset, int32 length, int32& index,
5834a96bcdaSStephan Aßmus 	int32& paragraphCount)
5844a96bcdaSStephan Aßmus {
5854a96bcdaSStephan Aßmus 	if (length == 0)
5864a96bcdaSStephan Aßmus 		return B_OK;
5874a96bcdaSStephan Aßmus 
5884a96bcdaSStephan Aßmus 	int32 paragraphOffset;
5894a96bcdaSStephan Aßmus 	index = ParagraphIndexFor(textOffset, paragraphOffset);
5904a96bcdaSStephan Aßmus 	if (index < 0)
5914a96bcdaSStephan Aßmus 		return B_BAD_VALUE;
5924a96bcdaSStephan Aßmus 
5934a96bcdaSStephan Aßmus 	textOffset -= paragraphOffset;
5944a96bcdaSStephan Aßmus 	paragraphCount++;
5954a96bcdaSStephan Aßmus 
5964a96bcdaSStephan Aßmus 	// The paragraph at the text offset remains, even if the offset is at
5974a96bcdaSStephan Aßmus 	// the beginning of that paragraph. The idea is that the selection start
5984a96bcdaSStephan Aßmus 	// stays visually in the same place. Therefore, the paragraph at that
5994a96bcdaSStephan Aßmus 	// offset has to keep the paragraph style from that paragraph.
6004a96bcdaSStephan Aßmus 
6014a96bcdaSStephan Aßmus 	Paragraph resultParagraph(ParagraphAt(index));
6024a96bcdaSStephan Aßmus 	int32 paragraphLength = resultParagraph.Length();
6034a96bcdaSStephan Aßmus 	if (textOffset == 0 && length > paragraphLength) {
6044a96bcdaSStephan Aßmus 		length -= paragraphLength;
6054a96bcdaSStephan Aßmus 		paragraphLength = 0;
6064a96bcdaSStephan Aßmus 		resultParagraph.Clear();
6074a96bcdaSStephan Aßmus 	} else {
6084a96bcdaSStephan Aßmus 		int32 removeLength = std::min(length, paragraphLength - textOffset);
6094a96bcdaSStephan Aßmus 		resultParagraph.Remove(textOffset, removeLength);
6104a96bcdaSStephan Aßmus 		paragraphLength -= removeLength;
6114a96bcdaSStephan Aßmus 		length -= removeLength;
6124a96bcdaSStephan Aßmus 	}
6134a96bcdaSStephan Aßmus 
6144a96bcdaSStephan Aßmus 	if (textOffset == paragraphLength && length == 0
6154a96bcdaSStephan Aßmus 		&& index + 1 < fParagraphs.CountItems()) {
6164a96bcdaSStephan Aßmus 		// Line break between paragraphs got removed. Shift the next
6174a96bcdaSStephan Aßmus 		// paragraph's text spans into the resulting one.
6184a96bcdaSStephan Aßmus 
619*3d2fd2acSAndrew Lindesay 		const Paragraph& paragraph = ParagraphAt(index + 1);
620*3d2fd2acSAndrew Lindesay 		int32 spanCount = paragraph.CountTextSpans();
6214a96bcdaSStephan Aßmus 		for (int32 i = 0; i < spanCount; i++) {
622*3d2fd2acSAndrew Lindesay 			const TextSpan& span = paragraph.TextSpanAtIndex(i);
6234a96bcdaSStephan Aßmus 			resultParagraph.Append(span);
6244a96bcdaSStephan Aßmus 		}
6254a96bcdaSStephan Aßmus 		fParagraphs.Remove(index + 1);
6264a96bcdaSStephan Aßmus 		paragraphCount++;
6274a96bcdaSStephan Aßmus 	}
6284a96bcdaSStephan Aßmus 
6294a96bcdaSStephan Aßmus 	textOffset = 0;
6304a96bcdaSStephan Aßmus 
6314a96bcdaSStephan Aßmus 	while (length > 0 && index + 1 < fParagraphs.CountItems()) {
6324a96bcdaSStephan Aßmus 		paragraphCount++;
6334a96bcdaSStephan Aßmus 		const Paragraph& paragraph = ParagraphAt(index + 1);
6344a96bcdaSStephan Aßmus 		paragraphLength = paragraph.Length();
6354a96bcdaSStephan Aßmus 		// Remove paragraph in any case. If some of it remains, the last
6364a96bcdaSStephan Aßmus 		// paragraph to remove is reached, and the remaining spans are
6374a96bcdaSStephan Aßmus 		// transfered to the result parahraph.
6384a96bcdaSStephan Aßmus 		if (length >= paragraphLength) {
6394a96bcdaSStephan Aßmus 			length -= paragraphLength;
6404a96bcdaSStephan Aßmus 			fParagraphs.Remove(index);
6414a96bcdaSStephan Aßmus 		} else {
6424a96bcdaSStephan Aßmus 			// Last paragraph reached
6434a96bcdaSStephan Aßmus 			int32 removedLength = std::min(length, paragraphLength);
6444a96bcdaSStephan Aßmus 			Paragraph newParagraph(paragraph);
6454a96bcdaSStephan Aßmus 			fParagraphs.Remove(index + 1);
6464a96bcdaSStephan Aßmus 
6474a96bcdaSStephan Aßmus 			if (!newParagraph.Remove(0, removedLength))
6484a96bcdaSStephan Aßmus 				return B_NO_MEMORY;
6494a96bcdaSStephan Aßmus 
6504a96bcdaSStephan Aßmus 			// Transfer remaining spans to resultParagraph
651*3d2fd2acSAndrew Lindesay 			int32 spanCount = newParagraph.CountTextSpans();
6524a96bcdaSStephan Aßmus 			for (int32 i = 0; i < spanCount; i++) {
653*3d2fd2acSAndrew Lindesay 				const TextSpan& span = newParagraph.TextSpanAtIndex(i);
6544a96bcdaSStephan Aßmus 				resultParagraph.Append(span);
6554a96bcdaSStephan Aßmus 			}
6564a96bcdaSStephan Aßmus 
6574a96bcdaSStephan Aßmus 			break;
6584a96bcdaSStephan Aßmus 		}
6594a96bcdaSStephan Aßmus 	}
6604a96bcdaSStephan Aßmus 
6614a96bcdaSStephan Aßmus 	fParagraphs.Replace(index, resultParagraph);
6624a96bcdaSStephan Aßmus 
6634a96bcdaSStephan Aßmus 	return B_OK;
6644a96bcdaSStephan Aßmus }
6654a96bcdaSStephan Aßmus 
6664a96bcdaSStephan Aßmus 
6674a96bcdaSStephan Aßmus // #pragma mark - notifications
6684a96bcdaSStephan Aßmus 
6694a96bcdaSStephan Aßmus 
670d7f7bf2dSAxel Dörfler void
671d7f7bf2dSAxel Dörfler TextDocument::_NotifyTextChanging(TextChangingEvent& event) const
672d7f7bf2dSAxel Dörfler {
673d7f7bf2dSAxel Dörfler 	// Copy listener list to have a stable list in case listeners
674d7f7bf2dSAxel Dörfler 	// are added/removed from within the notification hook.
675d7f7bf2dSAxel Dörfler 	TextListenerList listeners(fTextListeners);
676d7f7bf2dSAxel Dörfler 	int32 count = listeners.CountItems();
677d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
678d7f7bf2dSAxel Dörfler 		const TextListenerRef& listener = listeners.ItemAtFast(i);
679779ab335SX512 		if (!listener.IsSet())
680d7f7bf2dSAxel Dörfler 			continue;
681d7f7bf2dSAxel Dörfler 		listener->TextChanging(event);
682d7f7bf2dSAxel Dörfler 		if (event.IsCanceled())
683d7f7bf2dSAxel Dörfler 			break;
684d7f7bf2dSAxel Dörfler 	}
685d7f7bf2dSAxel Dörfler }
686d7f7bf2dSAxel Dörfler 
687d7f7bf2dSAxel Dörfler 
688d7f7bf2dSAxel Dörfler void
689d7f7bf2dSAxel Dörfler TextDocument::_NotifyTextChanged(const TextChangedEvent& event) const
690d7f7bf2dSAxel Dörfler {
691d7f7bf2dSAxel Dörfler 	// Copy listener list to have a stable list in case listeners
692d7f7bf2dSAxel Dörfler 	// are added/removed from within the notification hook.
693d7f7bf2dSAxel Dörfler 	TextListenerList listeners(fTextListeners);
694d7f7bf2dSAxel Dörfler 	int32 count = listeners.CountItems();
695d7f7bf2dSAxel Dörfler 	for (int32 i = 0; i < count; i++) {
696d7f7bf2dSAxel Dörfler 		const TextListenerRef& listener = listeners.ItemAtFast(i);
697779ab335SX512 		if (!listener.IsSet())
698d7f7bf2dSAxel Dörfler 			continue;
699d7f7bf2dSAxel Dörfler 		listener->TextChanged(event);
700d7f7bf2dSAxel Dörfler 	}
701d7f7bf2dSAxel Dörfler }
702d7f7bf2dSAxel Dörfler 
7034bf45bfbSStephan Aßmus 
7044bf45bfbSStephan Aßmus void
7054bf45bfbSStephan Aßmus TextDocument::_NotifyUndoableEditHappened(const UndoableEditRef& edit) const
7064bf45bfbSStephan Aßmus {
7074bf45bfbSStephan Aßmus 	// Copy listener list to have a stable list in case listeners
7084bf45bfbSStephan Aßmus 	// are added/removed from within the notification hook.
7094bf45bfbSStephan Aßmus 	UndoListenerList listeners(fUndoListeners);
7104bf45bfbSStephan Aßmus 	int32 count = listeners.CountItems();
7114bf45bfbSStephan Aßmus 	for (int32 i = 0; i < count; i++) {
7124bf45bfbSStephan Aßmus 		const UndoableEditListenerRef& listener = listeners.ItemAtFast(i);
713779ab335SX512 		if (!listener.IsSet())
7144bf45bfbSStephan Aßmus 			continue;
7154bf45bfbSStephan Aßmus 		listener->UndoableEditHappened(this, edit);
7164bf45bfbSStephan Aßmus 	}
7174bf45bfbSStephan Aßmus }
718