xref: /haiku/src/apps/haikudepot/textview/Paragraph.cpp (revision 6eafb4b041ad79cb936b2041fdb9c56b1209cc10)
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 BString
264 Paragraph::Text() const
265 {
266 	BString result;
267 
268 	int32 count = fTextSpans.CountItems();
269 	for (int32 i = 0; i < count; i++)
270 		result << fTextSpans.ItemAtFast(i).Text();
271 
272 	return result;
273 }
274 
275 
276 BString
277 Paragraph::Text(int32 start, int32 length) const
278 {
279 	Paragraph subParagraph = SubParagraph(start, length);
280 	return subParagraph.Text();
281 }
282 
283 
284 Paragraph
285 Paragraph::SubParagraph(int32 start, int32 length) const
286 {
287 	if (start < 0)
288 		start = 0;
289 
290 	if (start == 0 && length == Length())
291 		return *this;
292 
293 	Paragraph result(fStyle);
294 
295 	int32 count = fTextSpans.CountItems();
296 	for (int32 i = 0; i < count; i++) {
297 		const TextSpan& span = fTextSpans.ItemAtFast(i);
298 		int32 spanLength = span.CountChars();
299 		if (spanLength == 0)
300 			continue;
301 		if (start > spanLength) {
302 			// Skip span if its before start
303 			start -= spanLength;
304 			continue;
305 		}
306 
307 		// Remaining span length after start
308 		spanLength -= start;
309 		int32 copyLength = std::min(spanLength, length);
310 
311 		if (start == 0 && length == spanLength)
312 			result.Append(span);
313 		else
314 			result.Append(span.SubSpan(start, copyLength));
315 
316 		length -= copyLength;
317 		if (length == 0)
318 			break;
319 
320 		// Next span is copied from its beginning
321 		start = 0;
322 	}
323 
324 	return result;
325 }
326 
327 
328 void
329 Paragraph::PrintToStream() const
330 {
331 	int32 spanCount = fTextSpans.CountItems();
332 	if (spanCount == 0) {
333 		printf("  <p/>\n");
334 		return;
335 	}
336 	printf("  <p>\n");
337 	for (int32 i = 0; i < spanCount; i++) {
338 		const TextSpan& span = fTextSpans.ItemAtFast(i);
339 		if (span.CountChars() == 0)
340 			printf("    <span/>\n");
341 		else {
342 			BString text = span.Text();
343 			text.ReplaceAll("\n", "\\n");
344 			printf("    <span>%s</span>\n", text.String());
345 		}
346 	}
347 	printf("  </p>\n");
348 }
349 
350 
351 // #pragma mark -
352 
353 
354 void
355 Paragraph::_InvalidateCachedLength()
356 {
357 	fCachedLength = -1;
358 }
359