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