xref: /haiku/src/apps/haikudepot/textview/TextDocumentLayout.cpp (revision 68ea01249e1e2088933cb12f9c28d4e5c5d1c9ef)
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.Get() != NULL)
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.Get() != NULL)
101 		fDocument->RemoveListener(fTextListener);
102 
103 	fDocument = document;
104 	_Init();
105 	fLayoutValid = false;
106 
107 	if (fDocument.Get() != NULL)
108 		fDocument->AddListener(fTextListener);
109 }
110 
111 
112 void
113 TextDocumentLayout::Invalidate()
114 {
115 	if (fDocument.Get() != NULL)
116 		InvalidateParagraphs(0, fDocument->Paragraphs().CountItems());
117 }
118 
119 
120 void
121 TextDocumentLayout::InvalidateParagraphs(int32 start, int32 count)
122 {
123 	if (start < 0 || count == 0 || fDocument.Get() == NULL)
124 		return;
125 
126 	fLayoutValid = false;
127 
128 	const ParagraphList& paragraphs = fDocument->Paragraphs();
129 
130 	while (count > 0) {
131 		if (start >= paragraphs.CountItems())
132 			break;
133 		const Paragraph& paragraph = paragraphs.ItemAtFast(start);
134 		if (start >= fParagraphLayouts.CountItems()) {
135 			ParagraphLayoutRef layout(new(std::nothrow) ParagraphLayout(
136 				paragraph), true);
137 			if (layout.Get() == NULL
138 				|| !fParagraphLayouts.Add(ParagraphLayoutInfo(0.0f, layout))) {
139 				fprintf(stderr, "TextDocumentLayout::InvalidateParagraphs() - "
140 					"out of memory\n");
141 				return;
142 			}
143 		} else {
144 			const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(
145 				start);
146 			info.layout->SetParagraph(paragraph);
147 		}
148 
149 		start++;
150 		count--;
151 	}
152 
153 	// Remove any extra paragraph layouts
154 	while (paragraphs.CountItems() < fParagraphLayouts.CountItems())
155 		fParagraphLayouts.Remove(fParagraphLayouts.CountItems() - 1);
156 }
157 
158 
159 void
160 TextDocumentLayout::SetWidth(float width)
161 {
162 	if (fWidth != width) {
163 		fWidth = width;
164 		fLayoutValid = false;
165 	}
166 }
167 
168 
169 float
170 TextDocumentLayout::Height()
171 {
172 	_ValidateLayout();
173 
174 	float height = 0.0f;
175 
176 	if (fParagraphLayouts.CountItems() > 0) {
177 		const ParagraphLayoutInfo& lastLayout = fParagraphLayouts.LastItem();
178 		height = lastLayout.y + lastLayout.layout->Height();
179 	}
180 
181 	return height;
182 }
183 
184 
185 void
186 TextDocumentLayout::Draw(BView* view, const BPoint& offset,
187 	const BRect& updateRect)
188 {
189 	_ValidateLayout();
190 
191 	int layoutCount = fParagraphLayouts.CountItems();
192 	for (int i = 0; i < layoutCount; i++) {
193 		const ParagraphLayoutInfo& layout = fParagraphLayouts.ItemAtFast(i);
194 		BPoint location(offset.x, offset.y + layout.y);
195 		if (location.y > updateRect.bottom)
196 			break;
197 		if (location.y + layout.layout->Height() > updateRect.top)
198 			layout.layout->Draw(view, location);
199 	}
200 }
201 
202 
203 int32
204 TextDocumentLayout::LineIndexForOffset(int32 textOffset)
205 {
206 	int32 index = _ParagraphLayoutIndexForOffset(textOffset);
207 	if (index >= 0) {
208 		int32 lineIndex = 0;
209 		for (int32 i = 0; i < index; i++) {
210 			lineIndex += fParagraphLayouts.ItemAtFast(i).layout->CountLines();
211 		}
212 
213 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index);
214 		return lineIndex + info.layout->LineIndexForOffset(textOffset);
215 	}
216 
217 	return 0;
218 }
219 
220 
221 int32
222 TextDocumentLayout::FirstOffsetOnLine(int32 lineIndex)
223 {
224 	int32 paragraphOffset;
225 	int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset);
226 	if (index >= 0) {
227 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index);
228 		return info.layout->FirstOffsetOnLine(lineIndex) + paragraphOffset;
229 	}
230 
231 	return 0;
232 }
233 
234 
235 int32
236 TextDocumentLayout::LastOffsetOnLine(int32 lineIndex)
237 {
238 	int32 paragraphOffset;
239 	int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset);
240 	if (index >= 0) {
241 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index);
242 		return info.layout->LastOffsetOnLine(lineIndex) + paragraphOffset;
243 	}
244 
245 	return 0;
246 }
247 
248 
249 int32
250 TextDocumentLayout::CountLines()
251 {
252 	_ValidateLayout();
253 
254 	int32 lineCount = 0;
255 
256 	int32 count = fParagraphLayouts.CountItems();
257 	for (int32 i = 0; i < count; i++) {
258 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(i);
259 		lineCount += info.layout->CountLines();
260 	}
261 
262 	return lineCount;
263 }
264 
265 
266 void
267 TextDocumentLayout::GetLineBounds(int32 lineIndex, float& x1, float& y1,
268 	float& x2, float& y2)
269 {
270 	int32 paragraphOffset;
271 	int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset);
272 	if (index >= 0) {
273 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index);
274 		info.layout->GetLineBounds(lineIndex, x1, y1, x2, y2);
275 		y1 += info.y;
276 		y2 += info.y;
277 		return;
278 	}
279 
280 	x1 = 0.0f;
281 	y1 = 0.0f;
282 	x2 = 0.0f;
283 	y2 = 0.0f;
284 }
285 
286 
287 void
288 TextDocumentLayout::GetTextBounds(int32 textOffset, float& x1, float& y1,
289 	float& x2, float& y2)
290 {
291 	int32 index = _ParagraphLayoutIndexForOffset(textOffset);
292 	if (index >= 0) {
293 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index);
294 		info.layout->GetTextBounds(textOffset, x1, y1, x2, y2);
295 		y1 += info.y;
296 		y2 += info.y;
297 		return;
298 	}
299 
300 	x1 = 0.0f;
301 	y1 = 0.0f;
302 	x2 = 0.0f;
303 	y2 = 0.0f;
304 }
305 
306 
307 int32
308 TextDocumentLayout::TextOffsetAt(float x, float y, bool& rightOfCenter)
309 {
310 	_ValidateLayout();
311 
312 	int32 textOffset = 0;
313 	rightOfCenter = false;
314 
315 	int32 paragraphs = fParagraphLayouts.CountItems();
316 	for (int32 i = 0; i < paragraphs; i++) {
317 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(i);
318 		if (y > info.y + info.layout->Height()) {
319 			textOffset += info.layout->CountGlyphs();
320 			continue;
321 		}
322 
323 		textOffset += info.layout->TextOffsetAt(x, y - info.y, rightOfCenter);
324 		break;
325 	}
326 
327 	return textOffset;
328 }
329 
330 // #pragma mark - private
331 
332 
333 void
334 TextDocumentLayout::_ValidateLayout()
335 {
336 	if (!fLayoutValid) {
337 		_Layout();
338 		fLayoutValid = true;
339 	}
340 }
341 
342 
343 void
344 TextDocumentLayout::_Init()
345 {
346 	fParagraphLayouts.Clear();
347 
348 	if (fDocument.Get() == NULL)
349 		return;
350 
351 	const ParagraphList& paragraphs = fDocument->Paragraphs();
352 
353 	int paragraphCount = paragraphs.CountItems();
354 	for (int i = 0; i < paragraphCount; i++) {
355 		const Paragraph& paragraph = paragraphs.ItemAtFast(i);
356 		ParagraphLayoutRef layout(new(std::nothrow) ParagraphLayout(paragraph),
357 			true);
358 		if (layout.Get() == NULL
359 			|| !fParagraphLayouts.Add(ParagraphLayoutInfo(0.0f, layout))) {
360 			fprintf(stderr, "TextDocumentLayout::_Layout() - out of memory\n");
361 			return;
362 		}
363 	}
364 }
365 
366 
367 void
368 TextDocumentLayout::_Layout()
369 {
370 	float y = 0.0f;
371 
372 	int layoutCount = fParagraphLayouts.CountItems();
373 	for (int i = 0; i < layoutCount; i++) {
374 		ParagraphLayoutInfo info = fParagraphLayouts.ItemAtFast(i);
375 		const ParagraphStyle& style = info.layout->Style();
376 
377 		if (i > 0)
378 			y += style.SpacingTop();
379 
380 		fParagraphLayouts.Replace(i, ParagraphLayoutInfo(y, info.layout));
381 
382 		info.layout->SetWidth(fWidth);
383 		y += info.layout->Height() + style.SpacingBottom();
384 	}
385 }
386 
387 
388 int32
389 TextDocumentLayout::_ParagraphLayoutIndexForOffset(int32& textOffset)
390 {
391 	_ValidateLayout();
392 
393 	int32 paragraphs = fParagraphLayouts.CountItems();
394 	for (int32 i = 0; i < paragraphs - 1; i++) {
395 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(i);
396 
397 		int32 length = info.layout->CountGlyphs();
398 		if (textOffset >= length) {
399 			textOffset -= length;
400 			continue;
401 		}
402 
403 		return i;
404 	}
405 
406 	if (paragraphs > 0) {
407 		const ParagraphLayoutInfo& info = fParagraphLayouts.LastItem();
408 
409 		// Return last paragraph if the textOffset is still within or
410 		// exactly behind the last valid offset in that paragraph.
411 		int32 length = info.layout->CountGlyphs();
412 		if (textOffset <= length)
413 			return paragraphs - 1;
414 	}
415 
416 	return -1;
417 }
418 
419 int32
420 TextDocumentLayout::_ParagraphLayoutIndexForLineIndex(int32& lineIndex,
421 	int32& paragraphOffset)
422 {
423 	_ValidateLayout();
424 
425 	paragraphOffset = 0;
426 	int32 paragraphs = fParagraphLayouts.CountItems();
427 	for (int32 i = 0; i < paragraphs; i++) {
428 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(i);
429 
430 		int32 lineCount = info.layout->CountLines();
431 		if (lineIndex >= lineCount) {
432 			lineIndex -= lineCount;
433 			paragraphOffset += info.layout->CountGlyphs();
434 			continue;
435 		}
436 
437 		return i;
438 	}
439 
440 	return -1;
441 }
442