xref: /haiku/src/apps/haikudepot/textview/TextDocumentLayout.cpp (revision 410ed2fbba58819ac21e27d3676739728416761d)
1 /*
2  * Copyright 2013-2015, Stephan Aßmus <superstippi@gmx.de>.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 #include "TextDocumentLayout.h"
7 
8 #include <new>
9 #include <stdio.h>
10 
11 #include <View.h>
12 
13 
14 class LayoutTextListener : public TextListener {
15 public:
16 	LayoutTextListener(TextDocumentLayout* layout)
17 		:
18 		fLayout(layout)
19 	{
20 	}
21 
22 	virtual	void TextChanging(TextChangingEvent& event)
23 	{
24 	}
25 
26 	virtual	void TextChanged(const TextChangedEvent& event)
27 	{
28 //		printf("TextChanged(%" B_PRIi32 ", %" B_PRIi32 ")\n",
29 //			event.FirstChangedParagraph(),
30 //			event.ChangedParagraphCount());
31 		// TODO: The event does not contain useful data. Make the event
32 		// system work so only the affected paragraphs are updated.
33 		// I think we need "first affected", "last affected" (both relative
34 		// to the original paragraph count), and than how many new paragraphs
35 		// are between these. From the difference in old number of paragraphs
36 		// inbetween and the new count, we know how many new paragraphs are
37 		// missing, and the rest in the range needs to be updated.
38 //		fLayout->InvalidateParagraphs(event.FirstChangedParagraph(),
39 //			event.ChangedParagraphCount());
40 		fLayout->Invalidate();
41 	}
42 
43 private:
44 	TextDocumentLayout*	fLayout;
45 };
46 
47 
48 
49 TextDocumentLayout::TextDocumentLayout()
50 	:
51 	fWidth(0.0f),
52 	fLayoutValid(false),
53 
54 	fDocument(),
55 	fTextListener(new(std::nothrow) LayoutTextListener(this), true),
56 	fParagraphLayouts()
57 {
58 }
59 
60 
61 TextDocumentLayout::TextDocumentLayout(const TextDocumentRef& document)
62 	:
63 	fWidth(0.0f),
64 	fLayoutValid(false),
65 
66 	fDocument(),
67 	fTextListener(new(std::nothrow) LayoutTextListener(this), true),
68 	fParagraphLayouts()
69 {
70 	SetTextDocument(document);
71 }
72 
73 
74 TextDocumentLayout::TextDocumentLayout(const TextDocumentLayout& other)
75 	:
76 	fWidth(other.fWidth),
77 	fLayoutValid(other.fLayoutValid),
78 
79 	fDocument(other.fDocument),
80 	fTextListener(new(std::nothrow) LayoutTextListener(this), true),
81 	fParagraphLayouts(other.fParagraphLayouts)
82 {
83 	if (fDocument.IsSet())
84 		fDocument->AddListener(fTextListener);
85 }
86 
87 
88 TextDocumentLayout::~TextDocumentLayout()
89 {
90 	SetTextDocument(NULL);
91 }
92 
93 
94 void
95 TextDocumentLayout::SetTextDocument(const TextDocumentRef& document)
96 {
97 	if (fDocument == document)
98 		return;
99 
100 	if (fDocument.IsSet())
101 		fDocument->RemoveListener(fTextListener);
102 
103 	fDocument = document;
104 	_Init();
105 	fLayoutValid = false;
106 
107 	if (fDocument.IsSet())
108 		fDocument->AddListener(fTextListener);
109 }
110 
111 
112 void
113 TextDocumentLayout::Invalidate()
114 {
115 	if (fDocument.IsSet())
116 		InvalidateParagraphs(0, fDocument->CountParagraphs());
117 }
118 
119 
120 void
121 TextDocumentLayout::InvalidateParagraphs(int32 start, int32 count)
122 {
123 	if (start < 0 || count == 0 || !fDocument.IsSet())
124 		return;
125 
126 	fLayoutValid = false;
127 
128 	while (count > 0) {
129 		const int32 paragraphCount = fDocument->CountParagraphs();
130 		if (start >= paragraphCount)
131 			break;
132 		const Paragraph& paragraph = fDocument->ParagraphAtIndex(start);
133 		if (start >= static_cast<int32>(fParagraphLayouts.size())) {
134 			ParagraphLayoutRef layout(new(std::nothrow) ParagraphLayout(
135 				paragraph), true);
136 			if (!layout.IsSet()) {
137 				fprintf(stderr, "TextDocumentLayout::InvalidateParagraphs() - "
138 					"out of memory\n");
139 				return;
140 			}
141 			try {
142 				fParagraphLayouts.push_back(ParagraphLayoutInfo(0.0f, layout));
143 			}
144 			catch (std::bad_alloc& ba) {
145 				fprintf(stderr, "bad_alloc when invalidating paragraphs\n");
146 				return;
147 			}
148 		} else {
149 			const ParagraphLayoutInfo& info = fParagraphLayouts[start];
150 			info.layout->SetParagraph(paragraph);
151 		}
152 
153 		start++;
154 		count--;
155 	}
156 
157 	// Remove any extra paragraph layouts
158 	while (fDocument->CountParagraphs()
159 			< static_cast<int32>(fParagraphLayouts.size()))
160 		fParagraphLayouts.erase(fParagraphLayouts.end() - 1);
161 }
162 
163 
164 void
165 TextDocumentLayout::SetWidth(float width)
166 {
167 	if (fWidth != width) {
168 		fWidth = width;
169 		fLayoutValid = false;
170 	}
171 }
172 
173 
174 float
175 TextDocumentLayout::Height()
176 {
177 	_ValidateLayout();
178 
179 	float height = 0.0f;
180 
181 	if (fParagraphLayouts.size() > 0) {
182 		const ParagraphLayoutInfo& lastLayout
183 			= fParagraphLayouts[fParagraphLayouts.size() - 1];
184 		height = lastLayout.y + lastLayout.layout->Height();
185 	}
186 
187 	return height;
188 }
189 
190 
191 void
192 TextDocumentLayout::Draw(BView* view, const BPoint& offset,
193 	const BRect& updateRect)
194 {
195 	_ValidateLayout();
196 
197 	int layoutCount = fParagraphLayouts.size();
198 	for (int i = 0; i < layoutCount; i++) {
199 		const ParagraphLayoutInfo& layout = fParagraphLayouts[i];
200 		BPoint location(offset.x, offset.y + layout.y);
201 		if (location.y > updateRect.bottom)
202 			break;
203 		if (location.y + layout.layout->Height() > updateRect.top)
204 			layout.layout->Draw(view, location);
205 	}
206 }
207 
208 
209 int32
210 TextDocumentLayout::LineIndexForOffset(int32 textOffset)
211 {
212 	int32 index = _ParagraphLayoutIndexForOffset(textOffset);
213 	if (index >= 0) {
214 		int32 lineIndex = 0;
215 		for (int32 i = 0; i < index; i++) {
216 			lineIndex += fParagraphLayouts[i].layout->CountLines();
217 		}
218 
219 		const ParagraphLayoutInfo& info = fParagraphLayouts[index];
220 		return lineIndex + info.layout->LineIndexForOffset(textOffset);
221 	}
222 
223 	return 0;
224 }
225 
226 
227 int32
228 TextDocumentLayout::FirstOffsetOnLine(int32 lineIndex)
229 {
230 	int32 paragraphOffset;
231 	int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset);
232 	if (index >= 0) {
233 		const ParagraphLayoutInfo& info = fParagraphLayouts[index];
234 		return info.layout->FirstOffsetOnLine(lineIndex) + paragraphOffset;
235 	}
236 
237 	return 0;
238 }
239 
240 
241 int32
242 TextDocumentLayout::LastOffsetOnLine(int32 lineIndex)
243 {
244 	int32 paragraphOffset;
245 	int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset);
246 	if (index >= 0) {
247 		const ParagraphLayoutInfo& info = fParagraphLayouts[index];
248 		return info.layout->LastOffsetOnLine(lineIndex) + paragraphOffset;
249 	}
250 
251 	return 0;
252 }
253 
254 
255 int32
256 TextDocumentLayout::CountLines()
257 {
258 	_ValidateLayout();
259 
260 	int32 lineCount = 0;
261 
262 	int32 count = fParagraphLayouts.size();
263 	for (int32 i = 0; i < count; i++) {
264 		const ParagraphLayoutInfo& info = fParagraphLayouts[i];
265 		lineCount += info.layout->CountLines();
266 	}
267 
268 	return lineCount;
269 }
270 
271 
272 void
273 TextDocumentLayout::GetLineBounds(int32 lineIndex, float& x1, float& y1,
274 	float& x2, float& y2)
275 {
276 	int32 paragraphOffset;
277 	int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset);
278 	if (index >= 0) {
279 		const ParagraphLayoutInfo& info = fParagraphLayouts[index];
280 		info.layout->GetLineBounds(lineIndex, x1, y1, x2, y2);
281 		y1 += info.y;
282 		y2 += info.y;
283 		return;
284 	}
285 
286 	x1 = 0.0f;
287 	y1 = 0.0f;
288 	x2 = 0.0f;
289 	y2 = 0.0f;
290 }
291 
292 
293 void
294 TextDocumentLayout::GetTextBounds(int32 textOffset, float& x1, float& y1,
295 	float& x2, float& y2)
296 {
297 	int32 index = _ParagraphLayoutIndexForOffset(textOffset);
298 	if (index >= 0) {
299 		const ParagraphLayoutInfo& info = fParagraphLayouts[index];
300 		info.layout->GetTextBounds(textOffset, x1, y1, x2, y2);
301 		y1 += info.y;
302 		y2 += info.y;
303 		return;
304 	}
305 
306 	x1 = 0.0f;
307 	y1 = 0.0f;
308 	x2 = 0.0f;
309 	y2 = 0.0f;
310 }
311 
312 
313 int32
314 TextDocumentLayout::TextOffsetAt(float x, float y, bool& rightOfCenter)
315 {
316 	_ValidateLayout();
317 
318 	int32 textOffset = 0;
319 	rightOfCenter = false;
320 
321 	int32 paragraphs = fParagraphLayouts.size();
322 	for (int32 i = 0; i < paragraphs; i++) {
323 		const ParagraphLayoutInfo& info = fParagraphLayouts[i];
324 		if (y > info.y + info.layout->Height()) {
325 			textOffset += info.layout->CountGlyphs();
326 			continue;
327 		}
328 
329 		textOffset += info.layout->TextOffsetAt(x, y - info.y, rightOfCenter);
330 		break;
331 	}
332 
333 	return textOffset;
334 }
335 
336 // #pragma mark - private
337 
338 
339 void
340 TextDocumentLayout::_ValidateLayout()
341 {
342 	if (!fLayoutValid) {
343 		_Layout();
344 		fLayoutValid = true;
345 	}
346 }
347 
348 
349 void
350 TextDocumentLayout::_Init()
351 {
352 	fParagraphLayouts.clear();
353 
354 	if (!fDocument.IsSet())
355 		return;
356 
357 	int paragraphCount = fDocument->CountParagraphs();
358 	for (int i = 0; i < paragraphCount; i++) {
359 		const Paragraph& paragraph = fDocument->ParagraphAtIndex(i);
360 		ParagraphLayoutRef layout(new(std::nothrow) ParagraphLayout(paragraph),
361 			true);
362 		if (!layout.IsSet()) {
363 			fprintf(stderr, "TextDocumentLayout::_Layout() - out of memory\n");
364 			return;
365 		}
366 		try {
367 			fParagraphLayouts.push_back(ParagraphLayoutInfo(0.0f, layout));
368 		}
369 		catch (std::bad_alloc& ba) {
370 			fprintf(stderr, "bad_alloc when inititalizing the text document "
371 				"layout\n");
372 			return;
373 		}
374 	}
375 }
376 
377 
378 void
379 TextDocumentLayout::_Layout()
380 {
381 	float y = 0.0f;
382 
383 	int layoutCount = fParagraphLayouts.size();
384 	for (int i = 0; i < layoutCount; i++) {
385 		ParagraphLayoutInfo info = fParagraphLayouts[i];
386 		const ParagraphStyle& style = info.layout->Style();
387 
388 		if (i > 0)
389 			y += style.SpacingTop();
390 
391 		fParagraphLayouts[i] = ParagraphLayoutInfo(y, info.layout);
392 
393 		info.layout->SetWidth(fWidth);
394 		y += info.layout->Height() + style.SpacingBottom();
395 	}
396 }
397 
398 
399 int32
400 TextDocumentLayout::_ParagraphLayoutIndexForOffset(int32& textOffset)
401 {
402 	_ValidateLayout();
403 
404 	int32 paragraphs = fParagraphLayouts.size();
405 	for (int32 i = 0; i < paragraphs - 1; i++) {
406 		const ParagraphLayoutInfo& info = fParagraphLayouts[i];
407 
408 		int32 length = info.layout->CountGlyphs();
409 		if (textOffset >= length) {
410 			textOffset -= length;
411 			continue;
412 		}
413 
414 		return i;
415 	}
416 
417 	if (paragraphs > 0) {
418 		const ParagraphLayoutInfo& info
419 			= fParagraphLayouts[fParagraphLayouts.size() - 1];
420 
421 		// Return last paragraph if the textOffset is still within or
422 		// exactly behind the last valid offset in that paragraph.
423 		int32 length = info.layout->CountGlyphs();
424 		if (textOffset <= length)
425 			return paragraphs - 1;
426 	}
427 
428 	return -1;
429 }
430 
431 int32
432 TextDocumentLayout::_ParagraphLayoutIndexForLineIndex(int32& lineIndex,
433 	int32& paragraphOffset)
434 {
435 	_ValidateLayout();
436 
437 	paragraphOffset = 0;
438 	int32 paragraphs = fParagraphLayouts.size();
439 	for (int32 i = 0; i < paragraphs; i++) {
440 		const ParagraphLayoutInfo& info = fParagraphLayouts[i];
441 
442 		int32 lineCount = info.layout->CountLines();
443 		if (lineIndex >= lineCount) {
444 			lineIndex -= lineCount;
445 			paragraphOffset += info.layout->CountGlyphs();
446 			continue;
447 		}
448 
449 		return i;
450 	}
451 
452 	return -1;
453 }
454