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