xref: /haiku/src/kits/interface/StringView.cpp (revision 68ea01249e1e2088933cb12f9c28d4e5c5d1c9ef)
1 /*
2  * Copyright 2001-2015, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus <superstippi@gmx.de>
7  *		Axel Dörfler, axeld@pinc-software.de
8  *		Frans van Nispen (xlr8@tref.nl)
9  *		Ingo Weinhold <ingo_weinhold@gmx.de>
10  */
11 
12 
13 //!	BStringView draws a non-editable text string.
14 
15 
16 #include <StringView.h>
17 
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 
22 #include <LayoutUtils.h>
23 #include <Message.h>
24 #include <PropertyInfo.h>
25 #include <StringList.h>
26 #include <View.h>
27 #include <Window.h>
28 
29 #include <binary_compatibility/Interface.h>
30 
31 
32 static property_info sPropertyList[] = {
33 	{
34 		"Text",
35 		{ B_GET_PROPERTY, B_SET_PROPERTY },
36 		{ B_DIRECT_SPECIFIER },
37 		NULL, 0,
38 		{ B_STRING_TYPE }
39 	},
40 	{
41 		"Alignment",
42 		{ B_GET_PROPERTY, B_SET_PROPERTY },
43 		{ B_DIRECT_SPECIFIER },
44 		NULL, 0,
45 		{ B_INT32_TYPE }
46 	},
47 
48 	{ 0 }
49 };
50 
51 
52 BStringView::BStringView(BRect frame, const char* name, const char* text,
53 	uint32 resizingMode, uint32 flags)
54 	:
55 	BView(frame, name, resizingMode, flags | B_FULL_UPDATE_ON_RESIZE),
56 	fText(text ? strdup(text) : NULL),
57 	fTruncation(B_NO_TRUNCATION),
58 	fAlign(B_ALIGN_LEFT),
59 	fPreferredSize(text ? _StringWidth(text) : 0.0, -1)
60 {
61 }
62 
63 
64 BStringView::BStringView(const char* name, const char* text, uint32 flags)
65 	:
66 	BView(name, flags | B_FULL_UPDATE_ON_RESIZE),
67 	fText(text ? strdup(text) : NULL),
68 	fTruncation(B_NO_TRUNCATION),
69 	fAlign(B_ALIGN_LEFT),
70 	fPreferredSize(text ? _StringWidth(text) : 0.0, -1)
71 {
72 }
73 
74 
75 BStringView::BStringView(BMessage* archive)
76 	:
77 	BView(archive),
78 	fText(NULL),
79 	fTruncation(B_NO_TRUNCATION),
80 	fPreferredSize(0, -1)
81 {
82 	fAlign = (alignment)archive->GetInt32("_align", B_ALIGN_LEFT);
83 	fTruncation = (uint32)archive->GetInt32("_truncation", B_NO_TRUNCATION);
84 
85 	const char* text = archive->GetString("_text", NULL);
86 
87 	SetText(text);
88 	SetFlags(Flags() | B_FULL_UPDATE_ON_RESIZE);
89 }
90 
91 
92 BStringView::~BStringView()
93 {
94 	free(fText);
95 }
96 
97 
98 // #pragma mark - Archiving methods
99 
100 
101 BArchivable*
102 BStringView::Instantiate(BMessage* data)
103 {
104 	if (!validate_instantiation(data, "BStringView"))
105 		return NULL;
106 
107 	return new BStringView(data);
108 }
109 
110 
111 status_t
112 BStringView::Archive(BMessage* data, bool deep) const
113 {
114 	status_t status = BView::Archive(data, deep);
115 
116 	if (status == B_OK && fText)
117 		status = data->AddString("_text", fText);
118 	if (status == B_OK && fTruncation != B_NO_TRUNCATION)
119 		status = data->AddInt32("_truncation", fTruncation);
120 	if (status == B_OK)
121 		status = data->AddInt32("_align", fAlign);
122 
123 	return status;
124 }
125 
126 
127 // #pragma mark - Hook methods
128 
129 
130 void
131 BStringView::AttachedToWindow()
132 {
133 	if (HasDefaultColors())
134 		SetHighUIColor(B_PANEL_TEXT_COLOR);
135 
136 	BView* parent = Parent();
137 
138 	if (parent != NULL) {
139 		float tint = B_NO_TINT;
140 		color_which which = parent->ViewUIColor(&tint);
141 
142 		if (which != B_NO_COLOR) {
143 			SetViewUIColor(which, tint);
144 			SetLowUIColor(which, tint);
145 		} else {
146 			SetViewColor(parent->ViewColor());
147 			SetLowColor(ViewColor());
148 		}
149 	}
150 
151 	if (ViewColor() == B_TRANSPARENT_COLOR)
152 		AdoptSystemColors();
153 }
154 
155 
156 void
157 BStringView::DetachedFromWindow()
158 {
159 	BView::DetachedFromWindow();
160 }
161 
162 
163 void
164 BStringView::AllAttached()
165 {
166 	BView::AllAttached();
167 }
168 
169 
170 void
171 BStringView::AllDetached()
172 {
173 	BView::AllDetached();
174 }
175 
176 
177 // #pragma mark - Layout methods
178 
179 
180 void
181 BStringView::MakeFocus(bool focus)
182 {
183 	BView::MakeFocus(focus);
184 }
185 
186 
187 void
188 BStringView::GetPreferredSize(float* _width, float* _height)
189 {
190 	_ValidatePreferredSize();
191 
192 	if (_width)
193 		*_width = fPreferredSize.width;
194 
195 	if (_height)
196 		*_height = fPreferredSize.height;
197 }
198 
199 
200 BSize
201 BStringView::MinSize()
202 {
203 	return BLayoutUtils::ComposeSize(ExplicitMinSize(),
204 		_ValidatePreferredSize());
205 }
206 
207 
208 BSize
209 BStringView::MaxSize()
210 {
211 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
212 		_ValidatePreferredSize());
213 }
214 
215 
216 BSize
217 BStringView::PreferredSize()
218 {
219 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
220 		_ValidatePreferredSize());
221 }
222 
223 
224 void
225 BStringView::ResizeToPreferred()
226 {
227 	float width, height;
228 	GetPreferredSize(&width, &height);
229 
230 	// Resize the width only for B_ALIGN_LEFT (if its large enough already, that is)
231 	if (Bounds().Width() > width && Alignment() != B_ALIGN_LEFT)
232 		width = Bounds().Width();
233 
234 	BView::ResizeTo(width, height);
235 }
236 
237 
238 BAlignment
239 BStringView::LayoutAlignment()
240 {
241 	return BLayoutUtils::ComposeAlignment(ExplicitAlignment(),
242 		BAlignment(fAlign, B_ALIGN_MIDDLE));
243 }
244 
245 
246 // #pragma mark - More hook methods
247 
248 
249 void
250 BStringView::FrameMoved(BPoint newPosition)
251 {
252 	BView::FrameMoved(newPosition);
253 }
254 
255 
256 void
257 BStringView::FrameResized(float newWidth, float newHeight)
258 {
259 	BView::FrameResized(newWidth, newHeight);
260 }
261 
262 
263 void
264 BStringView::Draw(BRect updateRect)
265 {
266 	if (!fText)
267 		return;
268 
269 	if (LowUIColor() == B_NO_COLOR)
270 		SetLowColor(ViewColor());
271 
272 	font_height fontHeight;
273 	GetFontHeight(&fontHeight);
274 
275 	BRect bounds = Bounds();
276 
277 	BStringList lines;
278 	BString(fText).Split("\n", false, lines);
279 	for (int i = 0; i < lines.CountStrings(); i++) {
280 		const char* text = lines.StringAt(i).String();
281 		float width = StringWidth(text);
282 		BString truncated;
283 		if (fTruncation != B_NO_TRUNCATION && width > bounds.Width()) {
284 			// The string needs to be truncated
285 			// TODO: we should cache this
286 			truncated = lines.StringAt(i);
287 			TruncateString(&truncated, fTruncation, bounds.Width());
288 			text = truncated.String();
289 			width = StringWidth(text);
290 		}
291 
292 		float y = (bounds.top + bounds.bottom - ceilf(fontHeight.descent))
293 			- ceilf(fontHeight.ascent + fontHeight.descent + fontHeight.leading)
294 				* (lines.CountStrings() - i - 1);
295 		float x;
296 		switch (fAlign) {
297 			case B_ALIGN_RIGHT:
298 				x = bounds.Width() - width;
299 				break;
300 
301 			case B_ALIGN_CENTER:
302 				x = (bounds.Width() - width) / 2.0;
303 				break;
304 
305 			default:
306 				x = 0.0;
307 				break;
308 		}
309 
310 		DrawString(text, BPoint(x, y));
311 	}
312 }
313 
314 
315 void
316 BStringView::MessageReceived(BMessage* message)
317 {
318 	if (message->what == B_GET_PROPERTY || message->what == B_SET_PROPERTY) {
319 		int32 index;
320 		BMessage specifier;
321 		int32 form;
322 		const char* property;
323 		if (message->GetCurrentSpecifier(&index, &specifier, &form, &property)
324 				!= B_OK) {
325 			BView::MessageReceived(message);
326 			return;
327 		}
328 
329 		BMessage reply(B_REPLY);
330 		bool handled = false;
331 		if (strcmp(property, "Text") == 0) {
332 			if (message->what == B_GET_PROPERTY) {
333 				reply.AddString("result", fText);
334 				handled = true;
335 			} else {
336 				const char* text;
337 				if (message->FindString("data", &text) == B_OK) {
338 					SetText(text);
339 					reply.AddInt32("error", B_OK);
340 					handled = true;
341 				}
342 			}
343 		} else if (strcmp(property, "Alignment") == 0) {
344 			if (message->what == B_GET_PROPERTY) {
345 				reply.AddInt32("result", (int32)fAlign);
346 				handled = true;
347 			} else {
348 				int32 align;
349 				if (message->FindInt32("data", &align) == B_OK) {
350 					SetAlignment((alignment)align);
351 					reply.AddInt32("error", B_OK);
352 					handled = true;
353 				}
354 			}
355 		}
356 
357 		if (handled) {
358 			message->SendReply(&reply);
359 			return;
360 		}
361 	}
362 
363 	BView::MessageReceived(message);
364 }
365 
366 
367 void
368 BStringView::MouseDown(BPoint point)
369 {
370 	BView::MouseDown(point);
371 }
372 
373 
374 void
375 BStringView::MouseUp(BPoint point)
376 {
377 	BView::MouseUp(point);
378 }
379 
380 
381 void
382 BStringView::MouseMoved(BPoint point, uint32 transit, const BMessage* msg)
383 {
384 	BView::MouseMoved(point, transit, msg);
385 }
386 
387 
388 // #pragma mark -
389 
390 
391 void
392 BStringView::SetText(const char* text)
393 {
394 	if ((text && fText && !strcmp(text, fText)) || (!text && !fText))
395 		return;
396 
397 	free(fText);
398 	fText = text ? strdup(text) : NULL;
399 
400 	float newStringWidth = _StringWidth(fText);
401 	if (fPreferredSize.width != newStringWidth) {
402 		fPreferredSize.width = newStringWidth;
403 		InvalidateLayout();
404 	}
405 
406 	Invalidate();
407 }
408 
409 
410 const char*
411 BStringView::Text() const
412 {
413 	return fText;
414 }
415 
416 
417 void
418 BStringView::SetAlignment(alignment flag)
419 {
420 	fAlign = flag;
421 	Invalidate();
422 }
423 
424 
425 alignment
426 BStringView::Alignment() const
427 {
428 	return fAlign;
429 }
430 
431 
432 void
433 BStringView::SetTruncation(uint32 truncationMode)
434 {
435 	if (fTruncation != truncationMode) {
436 		fTruncation = truncationMode;
437 		Invalidate();
438 	}
439 }
440 
441 
442 uint32
443 BStringView::Truncation() const
444 {
445 	return fTruncation;
446 }
447 
448 
449 BHandler*
450 BStringView::ResolveSpecifier(BMessage* message, int32 index,
451 	BMessage* specifier, int32 form, const char* property)
452 {
453 	BPropertyInfo propInfo(sPropertyList);
454 	if (propInfo.FindMatch(message, 0, specifier, form, property) >= B_OK)
455 		return this;
456 
457 	return BView::ResolveSpecifier(message, index, specifier, form, property);
458 }
459 
460 
461 status_t
462 BStringView::GetSupportedSuites(BMessage* data)
463 {
464 	if (data == NULL)
465 		return B_BAD_VALUE;
466 
467 	status_t status = data->AddString("suites", "suite/vnd.Be-string-view");
468 	if (status != B_OK)
469 		return status;
470 
471 	BPropertyInfo propertyInfo(sPropertyList);
472 	status = data->AddFlat("messages", &propertyInfo);
473 	if (status != B_OK)
474 		return status;
475 
476 	return BView::GetSupportedSuites(data);
477 }
478 
479 
480 void
481 BStringView::SetFont(const BFont* font, uint32 mask)
482 {
483 	BView::SetFont(font, mask);
484 
485 	fPreferredSize.width = _StringWidth(fText);
486 
487 	Invalidate();
488 	InvalidateLayout();
489 }
490 
491 
492 void
493 BStringView::LayoutInvalidated(bool descendants)
494 {
495 	// invalidate cached preferred size
496 	fPreferredSize.height = -1;
497 }
498 
499 
500 // #pragma mark - Perform
501 
502 
503 status_t
504 BStringView::Perform(perform_code code, void* _data)
505 {
506 	switch (code) {
507 		case PERFORM_CODE_MIN_SIZE:
508 			((perform_data_min_size*)_data)->return_value
509 				= BStringView::MinSize();
510 			return B_OK;
511 
512 		case PERFORM_CODE_MAX_SIZE:
513 			((perform_data_max_size*)_data)->return_value
514 				= BStringView::MaxSize();
515 			return B_OK;
516 
517 		case PERFORM_CODE_PREFERRED_SIZE:
518 			((perform_data_preferred_size*)_data)->return_value
519 				= BStringView::PreferredSize();
520 			return B_OK;
521 
522 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
523 			((perform_data_layout_alignment*)_data)->return_value
524 				= BStringView::LayoutAlignment();
525 			return B_OK;
526 
527 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
528 			((perform_data_has_height_for_width*)_data)->return_value
529 				= BStringView::HasHeightForWidth();
530 			return B_OK;
531 
532 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
533 		{
534 			perform_data_get_height_for_width* data
535 				= (perform_data_get_height_for_width*)_data;
536 			BStringView::GetHeightForWidth(data->width, &data->min, &data->max,
537 				&data->preferred);
538 			return B_OK;
539 		}
540 
541 		case PERFORM_CODE_SET_LAYOUT:
542 		{
543 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
544 			BStringView::SetLayout(data->layout);
545 			return B_OK;
546 		}
547 
548 		case PERFORM_CODE_LAYOUT_INVALIDATED:
549 		{
550 			perform_data_layout_invalidated* data
551 				= (perform_data_layout_invalidated*)_data;
552 			BStringView::LayoutInvalidated(data->descendants);
553 			return B_OK;
554 		}
555 
556 		case PERFORM_CODE_DO_LAYOUT:
557 		{
558 			BStringView::DoLayout();
559 			return B_OK;
560 		}
561 	}
562 
563 	return BView::Perform(code, _data);
564 }
565 
566 
567 // #pragma mark - FBC padding methods
568 
569 
570 void BStringView::_ReservedStringView1() {}
571 void BStringView::_ReservedStringView2() {}
572 void BStringView::_ReservedStringView3() {}
573 
574 
575 // #pragma mark - Private methods
576 
577 
578 BStringView&
579 BStringView::operator=(const BStringView&)
580 {
581 	// Assignment not allowed (private)
582 	return *this;
583 }
584 
585 
586 BSize
587 BStringView::_ValidatePreferredSize()
588 {
589 	if (fPreferredSize.height < 0) {
590 		// height
591 		font_height fontHeight;
592 		GetFontHeight(&fontHeight);
593 
594 		int32 lines = 1;
595 		char* temp = fText ? strchr(fText, '\n') : NULL;
596 		while (temp != NULL) {
597 			temp = strchr(temp + 1, '\n');
598 			lines++;
599 		};
600 
601 		fPreferredSize.height = ceilf(fontHeight.ascent + fontHeight.descent
602 			+ fontHeight.leading) * lines;
603 
604 		ResetLayoutInvalidation();
605 	}
606 
607 	return fPreferredSize;
608 }
609 
610 
611 float
612 BStringView::_StringWidth(const char* text)
613 {
614 	if(text == NULL)
615 		return 0.0f;
616 
617 	float maxWidth = 0.0f;
618 	BStringList lines;
619 	BString(fText).Split("\n", false, lines);
620 	for (int i = 0; i < lines.CountStrings(); i++) {
621 		float width = StringWidth(lines.StringAt(i));
622 		if (maxWidth < width)
623 			maxWidth = width;
624 	}
625 	return maxWidth;
626 }
627 
628 
629 extern "C" void
630 B_IF_GCC_2(InvalidateLayout__11BStringViewb,
631 	_ZN11BStringView16InvalidateLayoutEb)(BView* view, bool descendants)
632 {
633 	perform_data_layout_invalidated data;
634 	data.descendants = descendants;
635 
636 	view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);
637 }
638