xref: /haiku/src/apps/haikudepot/textview/Paragraph.cpp (revision 1e60bdeab63fa7a57bc9a55b032052e95a18bd2c)
1 /*
2  * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 #include "Paragraph.h"
7 
8 #include <algorithm>
9 #include <stdio.h>
10 
11 
12 Paragraph::Paragraph()
13 	:
14 	fStyle()
15 {
16 }
17 
18 
19 Paragraph::Paragraph(const ParagraphStyle& style)
20 	:
21 	fStyle(style),
22 	fTextSpans(),
23 	fCachedLength(-1)
24 {
25 }
26 
27 
28 Paragraph::Paragraph(const Paragraph& other)
29 	:
30 	fStyle(other.fStyle),
31 	fTextSpans(other.fTextSpans),
32 	fCachedLength(other.fCachedLength)
33 {
34 }
35 
36 
37 Paragraph&
38 Paragraph::operator=(const Paragraph& other)
39 {
40 	fStyle = other.fStyle;
41 	fTextSpans = other.fTextSpans;
42 	fCachedLength = other.fCachedLength;
43 
44 	return *this;
45 }
46 
47 
48 bool
49 Paragraph::operator==(const Paragraph& other) const
50 {
51 	if (this == &other)
52 		return true;
53 
54 	return fStyle == other.fStyle
55 		&& fTextSpans == other.fTextSpans;
56 }
57 
58 
59 bool
60 Paragraph::operator!=(const Paragraph& other) const
61 {
62 	return !(*this == other);
63 }
64 
65 
66 void
67 Paragraph::SetStyle(const ParagraphStyle& style)
68 {
69 	fStyle = style;
70 }
71 
72 
73 bool
74 Paragraph::Prepend(const TextSpan& span)
75 {
76 	_InvalidateCachedLength();
77 
78 	// Try to merge with first span if the TextStyles are equal
79 	if (fTextSpans.CountItems() > 0) {
80 		const TextSpan& firstSpan = fTextSpans.ItemAtFast(0);
81 		if (firstSpan.Style() == span.Style()) {
82 			BString text(span.Text());
83 			text.Append(firstSpan.Text());
84 			return fTextSpans.Replace(0, TextSpan(text, span.Style()));
85 		}
86 	}
87 	return fTextSpans.Add(span, 0);
88 }
89 
90 
91 bool
92 Paragraph::Append(const TextSpan& span)
93 {
94 	_InvalidateCachedLength();
95 
96 	// Try to merge with last span if the TextStyles are equal
97 	if (fTextSpans.CountItems() > 0) {
98 		const TextSpan& lastSpan = fTextSpans.LastItem();
99 		if (lastSpan.Style() == span.Style()) {
100 			BString text(lastSpan.Text());
101 			text.Append(span.Text());
102 			fTextSpans.Remove();
103 			return fTextSpans.Add(TextSpan(text, span.Style()));
104 		}
105 	}
106 	return fTextSpans.Add(span);
107 }
108 
109 
110 bool
111 Paragraph::Insert(int32 offset, const TextSpan& newSpan)
112 {
113 	_InvalidateCachedLength();
114 
115 	int32 index = 0;
116 	while (index < fTextSpans.CountItems()) {
117 		const TextSpan& span = fTextSpans.ItemAtFast(index);
118 		if (offset - span.CountChars() < 0)
119 			break;
120 		offset -= span.CountChars();
121 		index++;
122 	}
123 
124 	if (fTextSpans.CountItems() == index)
125 		return Append(newSpan);
126 
127 	// Try to merge with span at index if the TextStyles are equal
128 	TextSpan span = fTextSpans.ItemAtFast(index);
129 	if (span.Style() == newSpan.Style()) {
130 		span.Insert(offset, newSpan.Text());
131 		return fTextSpans.Replace(index, span);
132 	}
133 
134 	if (offset == 0) {
135 		if (index > 0) {
136 			// Try to merge with TextSpan before if offset == 0 && index > 0
137 			TextSpan span = fTextSpans.ItemAtFast(index - 1);
138 			if (span.Style() == newSpan.Style()) {
139 				span.Insert(span.CountChars(), newSpan.Text());
140 				return fTextSpans.Replace(index - 1, span);
141 			}
142 		}
143 		// Just insert the new span before the one at index
144 		return fTextSpans.Add(newSpan, index);
145 	}
146 
147 	// Split the span,
148 	TextSpan spanBefore = span.SubSpan(0, offset);
149 	TextSpan spanAfter = span.SubSpan(offset, span.CountChars() - offset);
150 
151 	return fTextSpans.Replace(index, spanBefore)
152 		&& fTextSpans.Add(newSpan, index + 1)
153 		&& fTextSpans.Add(spanAfter, index + 2);
154 }
155 
156 
157 bool
158 Paragraph::Remove(int32 offset, int32 length)
159 {
160 	if (length == 0)
161 		return true;
162 
163 	_InvalidateCachedLength();
164 
165 	int32 index = 0;
166 	while (index < fTextSpans.CountItems()) {
167 		const TextSpan& span = fTextSpans.ItemAtFast(index);
168 		if (offset - span.CountChars() < 0)
169 			break;
170 		offset -= span.CountChars();
171 		index++;
172 	}
173 
174 	if (index >= fTextSpans.CountItems())
175 		return false;
176 
177 	TextSpan span(fTextSpans.ItemAtFast(index));
178 	int32 removeLength = std::min(span.CountChars() - offset, length);
179 	span.Remove(offset, removeLength);
180 	length -= removeLength;
181 	index += 1;
182 
183 	// Remove more spans if necessary
184 	while (length > 0 && index < fTextSpans.CountItems()) {
185 		int32 spanLength = fTextSpans.ItemAtFast(index).CountChars();
186 		if (spanLength <= length) {
187 			fTextSpans.Remove(index);
188 			length -= spanLength;
189 		} else {
190 			// Reached last span
191 			removeLength = std::min(length, spanLength);
192 			TextSpan lastSpan = fTextSpans.ItemAtFast(index).SubSpan(
193 				removeLength, spanLength - removeLength);
194 			// Try to merge with first span, otherwise replace span at index
195 			if (lastSpan.Style() == span.Style()) {
196 				span.Insert(span.CountChars(), lastSpan.Text());
197 				fTextSpans.Remove(index);
198 			} else {
199 				fTextSpans.Replace(index, lastSpan);
200 			}
201 
202 			break;
203 		}
204 	}
205 
206 	// See if anything from the TextSpan at offset remained, keep it as empty
207 	// span if it is the last remaining span.
208 	index--;
209 	if (span.CountChars() > 0 || fTextSpans.CountItems() == 1) {
210 		fTextSpans.Replace(index, span);
211 	} else {
212 		fTextSpans.Remove(index);
213 		index--;
214 	}
215 
216 	// See if spans can be merged after one has been removed.
217 	if (index >= 0 && index + 1 < fTextSpans.CountItems()) {
218 		const TextSpan& span1 = fTextSpans.ItemAtFast(index);
219 		const TextSpan& span2 = fTextSpans.ItemAtFast(index + 1);
220 		if (span1.Style() == span2.Style()) {
221 			span = span1;
222 			span.Append(span2.Text());
223 			fTextSpans.Replace(index, span);
224 			fTextSpans.Remove(index + 1);
225 		}
226 	}
227 
228 	return true;
229 }
230 
231 
232 void
233 Paragraph::Clear()
234 {
235 	fTextSpans.Clear();
236 }
237 
238 
239 int32
240 Paragraph::Length() const
241 {
242 	if (fCachedLength >= 0)
243 		return fCachedLength;
244 
245 	int32 length = 0;
246 	for (int32 i = fTextSpans.CountItems() - 1; i >= 0; i--) {
247 		const TextSpan& span = fTextSpans.ItemAtFast(i);
248 		length += span.CountChars();
249 	}
250 
251 	fCachedLength = length;
252 	return length;
253 }
254 
255 
256 bool
257 Paragraph::IsEmpty() const
258 {
259 	return fTextSpans.CountItems() == 0;
260 }
261 
262 
263 bool
264 Paragraph::EndsWith(BString string) const
265 {
266 	int length = Length();
267 	int endLength = string.CountChars();
268 	int start = length - endLength;
269 	BString end = Text(start, endLength);
270 	return end == string;
271 }
272 
273 
274 BString
275 Paragraph::Text() const
276 {
277 	BString result;
278 
279 	int32 count = fTextSpans.CountItems();
280 	for (int32 i = 0; i < count; i++)
281 		result << fTextSpans.ItemAtFast(i).Text();
282 
283 	return result;
284 }
285 
286 
287 BString
288 Paragraph::Text(int32 start, int32 length) const
289 {
290 	Paragraph subParagraph = SubParagraph(start, length);
291 	return subParagraph.Text();
292 }
293 
294 
295 Paragraph
296 Paragraph::SubParagraph(int32 start, int32 length) const
297 {
298 	if (start < 0)
299 		start = 0;
300 
301 	if (start == 0 && length == Length())
302 		return *this;
303 
304 	Paragraph result(fStyle);
305 
306 	int32 count = fTextSpans.CountItems();
307 	for (int32 i = 0; i < count; i++) {
308 		const TextSpan& span = fTextSpans.ItemAtFast(i);
309 		int32 spanLength = span.CountChars();
310 		if (spanLength == 0)
311 			continue;
312 		if (start > spanLength) {
313 			// Skip span if its before start
314 			start -= spanLength;
315 			continue;
316 		}
317 
318 		// Remaining span length after start
319 		spanLength -= start;
320 		int32 copyLength = std::min(spanLength, length);
321 
322 		if (start == 0 && length == spanLength)
323 			result.Append(span);
324 		else
325 			result.Append(span.SubSpan(start, copyLength));
326 
327 		length -= copyLength;
328 		if (length == 0)
329 			break;
330 
331 		// Next span is copied from its beginning
332 		start = 0;
333 	}
334 
335 	return result;
336 }
337 
338 
339 void
340 Paragraph::PrintToStream() const
341 {
342 	int32 spanCount = fTextSpans.CountItems();
343 	if (spanCount == 0) {
344 		printf("  <p/>\n");
345 		return;
346 	}
347 	printf("  <p>\n");
348 	for (int32 i = 0; i < spanCount; i++) {
349 		const TextSpan& span = fTextSpans.ItemAtFast(i);
350 		if (span.CountChars() == 0)
351 			printf("    <span/>\n");
352 		else {
353 			BString text = span.Text();
354 			text.ReplaceAll("\n", "\\n");
355 			printf("    <span>%s</span>\n", text.String());
356 		}
357 	}
358 	printf("  </p>\n");
359 }
360 
361 
362 // #pragma mark -
363 
364 
365 void
366 Paragraph::_InvalidateCachedLength()
367 {
368 	fCachedLength = -1;
369 }
370