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
32static 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
52BStringView::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
64BStringView::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
75BStringView::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
92BStringView::~BStringView()
93{
94	free(fText);
95}
96
97
98// #pragma mark - Archiving methods
99
100
101BArchivable*
102BStringView::Instantiate(BMessage* data)
103{
104	if (!validate_instantiation(data, "BStringView"))
105		return NULL;
106
107	return new BStringView(data);
108}
109
110
111status_t
112BStringView::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
130void
131BStringView::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
156void
157BStringView::DetachedFromWindow()
158{
159	BView::DetachedFromWindow();
160}
161
162
163void
164BStringView::AllAttached()
165{
166	BView::AllAttached();
167}
168
169
170void
171BStringView::AllDetached()
172{
173	BView::AllDetached();
174}
175
176
177// #pragma mark - Layout methods
178
179
180void
181BStringView::MakeFocus(bool focus)
182{
183	BView::MakeFocus(focus);
184}
185
186
187void
188BStringView::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
200BSize
201BStringView::MinSize()
202{
203	return BLayoutUtils::ComposeSize(ExplicitMinSize(),
204		_ValidatePreferredSize());
205}
206
207
208BSize
209BStringView::MaxSize()
210{
211	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
212		_ValidatePreferredSize());
213}
214
215
216BSize
217BStringView::PreferredSize()
218{
219	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
220		_ValidatePreferredSize());
221}
222
223
224void
225BStringView::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
238BAlignment
239BStringView::LayoutAlignment()
240{
241	return BLayoutUtils::ComposeAlignment(ExplicitAlignment(),
242		BAlignment(fAlign, B_ALIGN_MIDDLE));
243}
244
245
246// #pragma mark - More hook methods
247
248
249void
250BStringView::FrameMoved(BPoint newPosition)
251{
252	BView::FrameMoved(newPosition);
253}
254
255
256void
257BStringView::FrameResized(float newWidth, float newHeight)
258{
259	BView::FrameResized(newWidth, newHeight);
260}
261
262
263void
264BStringView::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
315void
316BStringView::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
367void
368BStringView::MouseDown(BPoint point)
369{
370	BView::MouseDown(point);
371}
372
373
374void
375BStringView::MouseUp(BPoint point)
376{
377	BView::MouseUp(point);
378}
379
380
381void
382BStringView::MouseMoved(BPoint point, uint32 transit, const BMessage* msg)
383{
384	BView::MouseMoved(point, transit, msg);
385}
386
387
388// #pragma mark -
389
390
391void
392BStringView::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
410const char*
411BStringView::Text() const
412{
413	return fText;
414}
415
416
417void
418BStringView::SetAlignment(alignment flag)
419{
420	fAlign = flag;
421	Invalidate();
422}
423
424
425alignment
426BStringView::Alignment() const
427{
428	return fAlign;
429}
430
431
432void
433BStringView::SetTruncation(uint32 truncationMode)
434{
435	if (fTruncation != truncationMode) {
436		fTruncation = truncationMode;
437		Invalidate();
438	}
439}
440
441
442uint32
443BStringView::Truncation() const
444{
445	return fTruncation;
446}
447
448
449BHandler*
450BStringView::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
461status_t
462BStringView::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
480void
481BStringView::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
492void
493BStringView::LayoutInvalidated(bool descendants)
494{
495	// invalidate cached preferred size
496	fPreferredSize.height = -1;
497}
498
499
500// #pragma mark - Perform
501
502
503status_t
504BStringView::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
570void BStringView::_ReservedStringView1() {}
571void BStringView::_ReservedStringView2() {}
572void BStringView::_ReservedStringView3() {}
573
574
575// #pragma mark - Private methods
576
577
578BStringView&
579BStringView::operator=(const BStringView&)
580{
581	// Assignment not allowed (private)
582	return *this;
583}
584
585
586BSize
587BStringView::_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
611float
612BStringView::_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
629extern "C" void
630B_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