1/*
2 * Copyright 2001-2020 Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Frans van Nispen (xlr8@tref.nl)
7 *		Stephan A��mus <superstippi@gmx.de>
8 *		Ingo Weinhold <bonefish@cs.tu-berlin.de>
9 *		John Scipione <jscipione@gmail.com>
10 */
11
12
13/*!	BTextControl displays text that can act like a control. */
14
15
16#include <TextControl.h>
17
18#include <string.h>
19
20#include <AbstractLayoutItem.h>
21#include <ControlLook.h>
22#include <LayoutUtils.h>
23#include <Message.h>
24#include <PropertyInfo.h>
25#include <Region.h>
26#include <Window.h>
27
28#include <binary_compatibility/Interface.h>
29#include <binary_compatibility/Support.h>
30
31#include "TextInput.h"
32
33
34//#define TRACE_TEXT_CONTROL
35#ifdef TRACE_TEXT_CONTROL
36#	include <stdio.h>
37#	include <FunctionTracer.h>
38	static int32 sFunctionDepth = -1;
39#	define CALLED(x...)	FunctionTracer _ft("BTextControl", __FUNCTION__, \
40							sFunctionDepth)
41#	define TRACE(x...)	{ BString _to; \
42							_to.Append(' ', (sFunctionDepth + 1) * 2); \
43							printf("%s", _to.String()); printf(x); }
44#else
45#	define CALLED(x...)
46#	define TRACE(x...)
47#endif
48
49
50namespace {
51	const char* const kFrameField = "BTextControl:layoutitem:frame";
52	const char* const kTextViewItemField = "BTextControl:textViewItem";
53	const char* const kLabelItemField = "BMenuField:labelItem";
54}
55
56
57static property_info sPropertyList[] = {
58	{
59		"Value",
60		{ B_GET_PROPERTY, B_SET_PROPERTY },
61		{ B_DIRECT_SPECIFIER },
62		NULL, 0,
63		{ B_STRING_TYPE }
64	},
65
66	{ 0 }
67};
68
69
70class BTextControl::LabelLayoutItem : public BAbstractLayoutItem {
71public:
72								LabelLayoutItem(BTextControl* parent);
73								LabelLayoutItem(BMessage* from);
74
75	virtual	bool				IsVisible();
76	virtual	void				SetVisible(bool visible);
77
78	virtual	BRect				Frame();
79	virtual	void				SetFrame(BRect frame);
80
81			void				SetParent(BTextControl* parent);
82	virtual	BView*				View();
83
84	virtual	BSize				BaseMinSize();
85	virtual	BSize				BaseMaxSize();
86	virtual	BSize				BasePreferredSize();
87	virtual	BAlignment			BaseAlignment();
88
89			BRect				FrameInParent() const;
90
91	virtual status_t			Archive(BMessage* into, bool deep = true) const;
92	static	BArchivable*		Instantiate(BMessage* from);
93
94private:
95			BTextControl*		fParent;
96			BRect				fFrame;
97};
98
99
100class BTextControl::TextViewLayoutItem : public BAbstractLayoutItem {
101public:
102								TextViewLayoutItem(BTextControl* parent);
103								TextViewLayoutItem(BMessage* from);
104
105	virtual	bool				IsVisible();
106	virtual	void				SetVisible(bool visible);
107
108	virtual	BRect				Frame();
109	virtual	void				SetFrame(BRect frame);
110
111			void				SetParent(BTextControl* parent);
112	virtual	BView*				View();
113
114	virtual	BSize				BaseMinSize();
115	virtual	BSize				BaseMaxSize();
116	virtual	BSize				BasePreferredSize();
117	virtual	BAlignment			BaseAlignment();
118
119			BRect				FrameInParent() const;
120
121	virtual status_t			Archive(BMessage* into, bool deep = true) const;
122	static	BArchivable*		Instantiate(BMessage* from);
123private:
124			BTextControl*		fParent;
125			BRect				fFrame;
126};
127
128
129struct BTextControl::LayoutData {
130	LayoutData(float width, float height)
131		:
132		label_layout_item(NULL),
133		text_view_layout_item(NULL),
134		previous_width(width),
135		previous_height(height),
136		valid(false)
137	{
138	}
139
140	LabelLayoutItem*	label_layout_item;
141	TextViewLayoutItem*	text_view_layout_item;
142	float				previous_width;		// used in FrameResized() for
143	float				previous_height;	// invalidation
144	font_height			font_info;
145	float				label_width;
146	float				label_height;
147	BSize				min;
148	BSize				text_view_min;
149	bool				valid;
150};
151
152
153static const int32 kFrameMargin = 2;
154static const int32 kLabelInputSpacing = 3;
155
156
157// #pragma mark - BTextControl
158
159
160BTextControl::BTextControl(BRect frame, const char* name, const char* label,
161	const char* text, BMessage* message, uint32 resizeMask, uint32 flags)
162	:
163	BControl(frame, name, label, message, resizeMask, flags | B_FRAME_EVENTS)
164{
165	_InitData(label);
166	_InitText(text);
167	_ValidateLayout();
168}
169
170
171BTextControl::BTextControl(const char* name, const char* label,
172	const char* text, BMessage* message, uint32 flags)
173	:
174	BControl(name, label, message, flags | B_FRAME_EVENTS)
175{
176	_InitData(label);
177	_InitText(text);
178	_ValidateLayout();
179}
180
181
182BTextControl::BTextControl(const char* label, const char* text,
183	BMessage* message)
184	:
185	BControl(NULL, label, message,
186		B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS)
187{
188	_InitData(label);
189	_InitText(text);
190	_ValidateLayout();
191}
192
193
194BTextControl::~BTextControl()
195{
196	SetModificationMessage(NULL);
197	delete fLayoutData;
198}
199
200
201//	#pragma mark - Archiving
202
203
204BTextControl::BTextControl(BMessage* archive)
205	:
206	BControl(BUnarchiver::PrepareArchive(archive))
207{
208	BUnarchiver unarchiver(archive);
209
210	_InitData(Label(), archive);
211
212	if (!BUnarchiver::IsArchiveManaged(archive))
213		_InitText(NULL, archive);
214
215	status_t err = B_OK;
216	if (archive->HasFloat("_divide"))
217		err = archive->FindFloat("_divide", &fDivider);
218
219	if (err == B_OK && archive->HasMessage("_mod_msg")) {
220		BMessage* message = new BMessage;
221		err = archive->FindMessage("_mod_msg", message);
222		SetModificationMessage(message);
223	}
224
225	unarchiver.Finish(err);
226}
227
228
229BArchivable*
230BTextControl::Instantiate(BMessage* archive)
231{
232	if (validate_instantiation(archive, "BTextControl"))
233		return new BTextControl(archive);
234
235	return NULL;
236}
237
238
239status_t
240BTextControl::Archive(BMessage* data, bool deep) const
241{
242	BArchiver archiver(data);
243	status_t result = BControl::Archive(data, deep);
244
245	alignment labelAlignment;
246	alignment textAlignment;
247	if (result == B_OK)
248		GetAlignment(&labelAlignment, &textAlignment);
249
250	if (result == B_OK)
251		result = data->AddInt32("_a_label", labelAlignment);
252
253	if (result == B_OK)
254		result = data->AddInt32("_a_text", textAlignment);
255
256	if (result == B_OK)
257		result = data->AddFloat("_divide", Divider());
258
259	if (result == B_OK && ModificationMessage() != NULL)
260		result = data->AddMessage("_mod_msg", ModificationMessage());
261
262	return archiver.Finish(result);
263}
264
265
266status_t
267BTextControl::AllArchived(BMessage* into) const
268{
269	BArchiver archiver(into);
270	status_t err = B_OK;
271
272	if (archiver.IsArchived(fLayoutData->text_view_layout_item)) {
273		err = archiver.AddArchivable(kTextViewItemField,
274			fLayoutData->text_view_layout_item);
275	}
276
277	if (err == B_OK && archiver.IsArchived(fLayoutData->label_layout_item)) {
278		err = archiver.AddArchivable(kLabelItemField,
279			fLayoutData->label_layout_item);
280	}
281
282	return err;
283}
284
285
286status_t
287BTextControl::AllUnarchived(const BMessage* from)
288{
289	status_t err;
290	if ((err = BControl::AllUnarchived(from)) != B_OK)
291		return err;
292
293	_InitText(NULL, from);
294
295	BUnarchiver unarchiver(from);
296	if (unarchiver.IsInstantiated(kTextViewItemField)) {
297		err = unarchiver.FindObject(kTextViewItemField,
298			BUnarchiver::B_DONT_ASSUME_OWNERSHIP,
299			fLayoutData->text_view_layout_item);
300
301		if (err == B_OK)
302			fLayoutData->text_view_layout_item->SetParent(this);
303		else
304			return err;
305	}
306
307	if (unarchiver.IsInstantiated(kLabelItemField)) {
308		err = unarchiver.FindObject(kLabelItemField,
309			BUnarchiver::B_DONT_ASSUME_OWNERSHIP,
310			fLayoutData->label_layout_item);
311
312		if (err == B_OK)
313			fLayoutData->label_layout_item->SetParent(this);
314	}
315	return err;
316}
317
318
319//	#pragma mark - Hook methods
320
321
322void
323BTextControl::AllAttached()
324{
325	BControl::AllAttached();
326}
327
328
329void
330BTextControl::AllDetached()
331{
332	BControl::AllDetached();
333}
334
335
336void
337BTextControl::AttachedToWindow()
338{
339	BControl::AttachedToWindow();
340
341	_UpdateTextViewColors(IsEnabled());
342	fText->MakeEditable(IsEnabled());
343}
344
345
346void
347BTextControl::DetachedFromWindow()
348{
349	BControl::DetachedFromWindow();
350}
351
352
353void
354BTextControl::Draw(BRect updateRect)
355{
356	bool enabled = IsEnabled();
357	bool active = fText->IsFocus() && Window()->IsActive();
358
359	BRect rect = fText->Frame();
360	rect.InsetBy(-2, -2);
361
362	rgb_color base = ViewColor();
363	uint32 flags = fLook;
364	if (!enabled)
365		flags |= BControlLook::B_DISABLED;
366
367	if (active)
368		flags |= BControlLook::B_FOCUSED;
369
370	be_control_look->DrawTextControlBorder(this, rect, updateRect, base,
371		flags);
372
373	if (Label() != NULL) {
374		if (fLayoutData->label_layout_item != NULL) {
375			rect = fLayoutData->label_layout_item->FrameInParent();
376		} else {
377			rect = Bounds();
378			rect.right = fDivider - kLabelInputSpacing;
379		}
380
381		// erase the is control flag before drawing the label so that the label
382		// will get drawn using B_PANEL_TEXT_COLOR
383		flags &= ~BControlLook::B_IS_CONTROL;
384
385		be_control_look->DrawLabel(this, Label(), rect, updateRect,
386			base, flags, BAlignment(fLabelAlign, B_ALIGN_MIDDLE));
387	}
388}
389
390
391void
392BTextControl::FrameMoved(BPoint newPosition)
393{
394	BControl::FrameMoved(newPosition);
395}
396
397
398void
399BTextControl::FrameResized(float width, float height)
400{
401	CALLED();
402
403	BControl::FrameResized(width, height);
404
405	// TODO: this causes flickering still...
406
407	// changes in width
408
409	BRect bounds = Bounds();
410
411	if (bounds.Width() > fLayoutData->previous_width) {
412		// invalidate the region between the old and the new right border
413		BRect rect = bounds;
414		rect.left += fLayoutData->previous_width - kFrameMargin;
415		rect.right--;
416		Invalidate(rect);
417	} else if (bounds.Width() < fLayoutData->previous_width) {
418		// invalidate the region of the new right border
419		BRect rect = bounds;
420		rect.left = rect.right - kFrameMargin;
421		Invalidate(rect);
422	}
423
424	// changes in height
425
426	if (bounds.Height() > fLayoutData->previous_height) {
427		// invalidate the region between the old and the new bottom border
428		BRect rect = bounds;
429		rect.top += fLayoutData->previous_height - kFrameMargin;
430		rect.bottom--;
431		Invalidate(rect);
432		// invalidate label area
433		rect = bounds;
434		rect.right = fDivider;
435		Invalidate(rect);
436	} else if (bounds.Height() < fLayoutData->previous_height) {
437		// invalidate the region of the new bottom border
438		BRect rect = bounds;
439		rect.top = rect.bottom - kFrameMargin;
440		Invalidate(rect);
441		// invalidate label area
442		rect = bounds;
443		rect.right = fDivider;
444		Invalidate(rect);
445	}
446
447	fLayoutData->previous_width = bounds.Width();
448	fLayoutData->previous_height = bounds.Height();
449
450	TRACE("width: %.2f, height: %.2f\n", bounds.Width(), bounds.Height());
451}
452
453
454status_t
455BTextControl::Invoke(BMessage* message)
456{
457	return BControl::Invoke(message);
458}
459
460
461void
462BTextControl::LayoutInvalidated(bool descendants)
463{
464	CALLED();
465
466	fLayoutData->valid = false;
467}
468
469
470void
471BTextControl::MessageReceived(BMessage* message)
472{
473	if (message->what == B_COLORS_UPDATED) {
474
475		if (message->HasColor(ui_color_name(B_PANEL_BACKGROUND_COLOR))
476			|| message->HasColor(ui_color_name(B_PANEL_TEXT_COLOR))
477			|| message->HasColor(ui_color_name(B_DOCUMENT_BACKGROUND_COLOR))
478			|| message->HasColor(ui_color_name(B_DOCUMENT_TEXT_COLOR))) {
479			_UpdateTextViewColors(IsEnabled());
480		}
481	}
482
483	if (message->what == B_GET_PROPERTY || message->what == B_SET_PROPERTY) {
484		BMessage reply(B_REPLY);
485		bool handled = false;
486
487		BMessage specifier;
488		int32 index;
489		int32 form;
490		const char* property;
491		if (message->GetCurrentSpecifier(&index, &specifier, &form, &property) == B_OK) {
492			if (strcmp(property, "Value") == 0) {
493				if (message->what == B_GET_PROPERTY) {
494					reply.AddString("result", fText->Text());
495					handled = true;
496				} else {
497					const char* value = NULL;
498					// B_SET_PROPERTY
499					if (message->FindString("data", &value) == B_OK) {
500						fText->SetText(value);
501						reply.AddInt32("error", B_OK);
502						handled = true;
503					}
504				}
505			}
506		}
507
508		if (handled) {
509			message->SendReply(&reply);
510			return;
511		}
512	}
513
514	BControl::MessageReceived(message);
515}
516
517
518void
519BTextControl::MouseDown(BPoint where)
520{
521	if (!fText->IsFocus())
522		fText->MakeFocus(true);
523}
524
525
526void
527BTextControl::MouseMoved(BPoint where, uint32 transit,
528	const BMessage* dragMessage)
529{
530	BControl::MouseMoved(where, transit, dragMessage);
531}
532
533
534void
535BTextControl::MouseUp(BPoint where)
536{
537	BControl::MouseUp(where);
538}
539
540
541void
542BTextControl::WindowActivated(bool active)
543{
544	if (fText->IsFocus()) {
545		// invalidate to remove/show focus indication
546		BRect rect = fText->Frame();
547		rect.InsetBy(-1, -1);
548		Invalidate(rect);
549
550		// help out embedded text view which doesn't
551		// get notified of this
552		fText->Invalidate();
553	}
554}
555
556
557//	#pragma mark - Getters and Setters
558
559
560void
561BTextControl::SetText(const char* text)
562{
563	if (InvokeKind() != B_CONTROL_INVOKED)
564		return;
565
566	CALLED();
567
568	fText->SetText(text);
569
570	if (fText->IsFocus()) {
571		fText->SetInitialText();
572		fText->SelectAll();
573	}
574
575	fText->Invalidate();
576}
577
578
579const char*
580BTextControl::Text() const
581{
582	return fText->Text();
583}
584
585
586int32
587BTextControl::TextLength() const
588{
589	return fText->TextLength();
590}
591
592
593void
594BTextControl::MarkAsInvalid(bool invalid)
595{
596	uint32 look = fLook;
597
598	if (invalid)
599		fLook |= BControlLook::B_INVALID;
600	else
601		fLook &= ~BControlLook::B_INVALID;
602
603	if (look != fLook)
604		Invalidate();
605}
606
607
608void
609BTextControl::SetValue(int32 value)
610{
611	BControl::SetValue(value);
612}
613
614
615BTextView*
616BTextControl::TextView() const
617{
618	return fText;
619}
620
621
622void
623BTextControl::SetModificationMessage(BMessage* message)
624{
625	delete fModificationMessage;
626	fModificationMessage = message;
627}
628
629
630BMessage*
631BTextControl::ModificationMessage() const
632{
633	return fModificationMessage;
634}
635
636
637void
638BTextControl::SetAlignment(alignment labelAlignment, alignment textAlignment)
639{
640	fText->SetAlignment(textAlignment);
641
642	if (fLabelAlign != labelAlignment) {
643		fLabelAlign = labelAlignment;
644		Invalidate();
645	}
646}
647
648
649void
650BTextControl::GetAlignment(alignment* _label, alignment* _text) const
651{
652	if (_label != NULL)
653		*_label = fLabelAlign;
654
655	if (_text != NULL)
656		*_text = fText->Alignment();
657}
658
659
660void
661BTextControl::SetDivider(float position)
662{
663	fDivider = floorf(position + 0.5);
664
665	_LayoutTextView();
666
667	if (Window()) {
668		fText->Invalidate();
669		Invalidate();
670	}
671}
672
673
674float
675BTextControl::Divider() const
676{
677	return fDivider;
678}
679
680
681void
682BTextControl::MakeFocus(bool state)
683{
684	if (state != fText->IsFocus()) {
685		fText->MakeFocus(state);
686
687		if (state)
688			fText->SelectAll();
689	}
690}
691
692
693void
694BTextControl::SetEnabled(bool enable)
695{
696	if (IsEnabled() == enable)
697		return;
698
699	if (Window() != NULL) {
700		fText->MakeEditable(enable);
701		if (enable)
702			fText->SetFlags(fText->Flags() | B_NAVIGABLE);
703		else
704			fText->SetFlags(fText->Flags() & ~B_NAVIGABLE);
705
706		_UpdateTextViewColors(enable);
707
708		fText->Invalidate();
709		Window()->UpdateIfNeeded();
710	}
711
712	BControl::SetEnabled(enable);
713}
714
715
716void
717BTextControl::GetPreferredSize(float* _width, float* _height)
718{
719	CALLED();
720
721	_ValidateLayoutData();
722
723	if (_width) {
724		float minWidth = fLayoutData->min.width;
725		if (Label() == NULL && !(Flags() & B_SUPPORTS_LAYOUT)) {
726			// Indeed, only if there is no label! BeOS backwards compatible
727			// behavior:
728			minWidth = max_c(minWidth, Bounds().Width());
729		}
730		*_width = minWidth;
731	}
732
733	if (_height)
734		*_height = fLayoutData->min.height;
735}
736
737
738void
739BTextControl::ResizeToPreferred()
740{
741	BView::ResizeToPreferred();
742
743	fDivider = 0.0;
744	const char* label = Label();
745	if (label)
746		fDivider = ceil(StringWidth(label)) + 2.0;
747
748	_LayoutTextView();
749}
750
751
752void
753BTextControl::SetFlags(uint32 flags)
754{
755	// If the textview is navigable, set it to not navigable if needed
756	// Else if it is not navigable, set it to navigable if needed
757	if (fText->Flags() & B_NAVIGABLE) {
758		if (!(flags & B_NAVIGABLE))
759			fText->SetFlags(fText->Flags() & ~B_NAVIGABLE);
760
761	} else {
762		if (flags & B_NAVIGABLE)
763			fText->SetFlags(fText->Flags() | B_NAVIGABLE);
764	}
765
766	// Don't make this one navigable
767	flags &= ~B_NAVIGABLE;
768
769	BView::SetFlags(flags);
770}
771
772
773//	#pragma mark - Scripting
774
775
776BHandler*
777BTextControl::ResolveSpecifier(BMessage* message, int32 index,
778	BMessage* specifier, int32 what, const char* property)
779{
780	BPropertyInfo propInfo(sPropertyList);
781
782	if (propInfo.FindMatch(message, 0, specifier, what, property) >= B_OK)
783		return this;
784
785	return BControl::ResolveSpecifier(message, index, specifier, what,
786		property);
787}
788
789
790status_t
791BTextControl::GetSupportedSuites(BMessage* data)
792{
793	return BControl::GetSupportedSuites(data);
794}
795
796
797//	#pragma mark - Layout
798
799
800BSize
801BTextControl::MinSize()
802{
803	CALLED();
804
805	_ValidateLayoutData();
806	return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
807}
808
809
810BSize
811BTextControl::MaxSize()
812{
813	CALLED();
814
815	_ValidateLayoutData();
816
817	BSize max = fLayoutData->min;
818	max.width = B_SIZE_UNLIMITED;
819
820	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max);
821}
822
823
824BSize
825BTextControl::PreferredSize()
826{
827	CALLED();
828
829	_ValidateLayoutData();
830	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), fLayoutData->min);
831}
832
833
834BAlignment
835BTextControl::LayoutAlignment()
836{
837	CALLED();
838
839	_ValidateLayoutData();
840	return BLayoutUtils::ComposeAlignment(ExplicitAlignment(),
841		BAlignment(B_ALIGN_LEFT, B_ALIGN_VERTICAL_CENTER));
842}
843
844
845BLayoutItem*
846BTextControl::CreateLabelLayoutItem()
847{
848	if (!fLayoutData->label_layout_item)
849		fLayoutData->label_layout_item = new LabelLayoutItem(this);
850
851	return fLayoutData->label_layout_item;
852}
853
854
855BLayoutItem*
856BTextControl::CreateTextViewLayoutItem()
857{
858	if (!fLayoutData->text_view_layout_item)
859		fLayoutData->text_view_layout_item = new TextViewLayoutItem(this);
860
861	return fLayoutData->text_view_layout_item;
862}
863
864
865void
866BTextControl::DoLayout()
867{
868	// Bail out, if we shan't do layout.
869	if (!(Flags() & B_SUPPORTS_LAYOUT))
870		return;
871
872	CALLED();
873
874	// If the user set a layout, we let the base class version call its
875	// hook.
876	if (GetLayout()) {
877		BView::DoLayout();
878		return;
879	}
880
881	_ValidateLayoutData();
882
883	// validate current size
884	BSize size(Bounds().Size());
885	if (size.width < fLayoutData->min.width)
886		size.width = fLayoutData->min.width;
887
888	if (size.height < fLayoutData->min.height)
889		size.height = fLayoutData->min.height;
890
891	BRect dirty(fText->Frame());
892	BRect textFrame;
893
894	// divider
895	float divider = 0;
896	if (fLayoutData->text_view_layout_item != NULL) {
897		if (fLayoutData->label_layout_item != NULL) {
898			// We have layout items. They define the divider location.
899			divider = fabs(fLayoutData->text_view_layout_item->Frame().left
900				- fLayoutData->label_layout_item->Frame().left);
901		}
902		textFrame = fLayoutData->text_view_layout_item->FrameInParent();
903	} else {
904		if (fLayoutData->label_width > 0) {
905			divider = fLayoutData->label_width
906				+ be_control_look->DefaultLabelSpacing();
907		}
908		textFrame.Set(divider, 0, size.width, size.height);
909	}
910
911	// place the text view and set the divider
912	textFrame.InsetBy(kFrameMargin, kFrameMargin);
913	BLayoutUtils::AlignInFrame(fText, textFrame);
914	fText->SetTextRect(textFrame.OffsetToCopy(B_ORIGIN));
915
916	fDivider = divider;
917
918	// invalidate dirty region
919	dirty = dirty | fText->Frame();
920	dirty.InsetBy(-kFrameMargin, -kFrameMargin);
921
922	Invalidate(dirty);
923}
924
925
926// #pragma mark - protected methods
927
928
929status_t
930BTextControl::SetIcon(const BBitmap* icon, uint32 flags)
931{
932	return BControl::SetIcon(icon, flags);
933}
934
935
936// #pragma mark - private methods
937
938
939status_t
940BTextControl::Perform(perform_code code, void* _data)
941{
942	switch (code) {
943		case PERFORM_CODE_MIN_SIZE:
944			((perform_data_min_size*)_data)->return_value
945				= BTextControl::MinSize();
946			return B_OK;
947
948		case PERFORM_CODE_MAX_SIZE:
949			((perform_data_max_size*)_data)->return_value
950				= BTextControl::MaxSize();
951			return B_OK;
952
953		case PERFORM_CODE_PREFERRED_SIZE:
954			((perform_data_preferred_size*)_data)->return_value
955				= BTextControl::PreferredSize();
956			return B_OK;
957
958		case PERFORM_CODE_LAYOUT_ALIGNMENT:
959			((perform_data_layout_alignment*)_data)->return_value
960				= BTextControl::LayoutAlignment();
961			return B_OK;
962
963		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
964			((perform_data_has_height_for_width*)_data)->return_value
965				= BTextControl::HasHeightForWidth();
966			return B_OK;
967
968		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
969		{
970			perform_data_get_height_for_width* data
971				= (perform_data_get_height_for_width*)_data;
972			BTextControl::GetHeightForWidth(data->width, &data->min, &data->max,
973				&data->preferred);
974			return B_OK;
975		}
976
977		case PERFORM_CODE_SET_LAYOUT:
978		{
979			perform_data_set_layout* data = (perform_data_set_layout*)_data;
980			BTextControl::SetLayout(data->layout);
981			return B_OK;
982		}
983
984		case PERFORM_CODE_LAYOUT_INVALIDATED:
985		{
986			perform_data_layout_invalidated* data
987				= (perform_data_layout_invalidated*)_data;
988			BTextControl::LayoutInvalidated(data->descendants);
989			return B_OK;
990		}
991
992		case PERFORM_CODE_DO_LAYOUT:
993		{
994			BTextControl::DoLayout();
995			return B_OK;
996		}
997
998		case PERFORM_CODE_SET_ICON:
999		{
1000			perform_data_set_icon* data = (perform_data_set_icon*)_data;
1001			return BTextControl::SetIcon(data->icon, data->flags);
1002		}
1003
1004		case PERFORM_CODE_ALL_UNARCHIVED:
1005		{
1006			perform_data_all_unarchived* data
1007				= (perform_data_all_unarchived*)_data;
1008			data->return_value = BTextControl::AllUnarchived(data->archive);
1009			return B_OK;
1010		}
1011
1012		case PERFORM_CODE_ALL_ARCHIVED:
1013		{
1014			perform_data_all_archived* data
1015				= (perform_data_all_archived*)_data;
1016			data->return_value = BTextControl::AllArchived(data->archive);
1017			return B_OK;
1018		}
1019	}
1020
1021	return BControl::Perform(code, _data);
1022}
1023
1024
1025//	#pragma mark - FBC padding
1026
1027
1028void BTextControl::_ReservedTextControl1() {}
1029void BTextControl::_ReservedTextControl2() {}
1030void BTextControl::_ReservedTextControl3() {}
1031void BTextControl::_ReservedTextControl4() {}
1032
1033
1034BTextControl&
1035BTextControl::operator=(const BTextControl&)
1036{
1037	return *this;
1038}
1039
1040
1041void
1042BTextControl::_UpdateTextViewColors(bool enable)
1043{
1044	rgb_color textColor = ui_color(B_DOCUMENT_TEXT_COLOR);
1045	rgb_color viewColor = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
1046	BFont font;
1047
1048	fText->GetFontAndColor(0, &font);
1049
1050	if (!enable) {
1051		textColor = disable_color(textColor, ViewColor());
1052		viewColor = disable_color(ViewColor(), viewColor);
1053	}
1054
1055	fText->SetFontAndColor(&font, B_FONT_ALL, &textColor);
1056	fText->SetViewColor(viewColor);
1057	fText->SetLowColor(viewColor);
1058}
1059
1060
1061void
1062BTextControl::_CommitValue()
1063{
1064}
1065
1066
1067void
1068BTextControl::_InitData(const char* label, const BMessage* archive)
1069{
1070	BRect bounds(Bounds());
1071
1072	fText = NULL;
1073	fModificationMessage = NULL;
1074	fLabelAlign = B_ALIGN_LEFT;
1075	fDivider = 0.0f;
1076	fLayoutData = new LayoutData(bounds.Width(), bounds.Height());
1077
1078	int32 flags = 0;
1079
1080	BFont font(be_plain_font);
1081
1082	if (!archive || !archive->HasString("_fname"))
1083		flags |= B_FONT_FAMILY_AND_STYLE;
1084
1085	if (!archive || !archive->HasFloat("_fflt"))
1086		flags |= B_FONT_SIZE;
1087
1088	if (flags != 0)
1089		SetFont(&font, flags);
1090
1091	if (label != NULL)
1092		fDivider = floorf(bounds.Width() / 2.0f);
1093
1094	fLook = 0;
1095}
1096
1097
1098void
1099BTextControl::_InitText(const char* initialText, const BMessage* archive)
1100{
1101	if (archive)
1102		fText = static_cast<BPrivate::_BTextInput_*>(FindView("_input_"));
1103
1104	if (fText == NULL) {
1105		BRect bounds(Bounds());
1106		BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom);
1107		// we are stroking the frame around the text view, which
1108		// is 2 pixels wide
1109		frame.InsetBy(kFrameMargin, kFrameMargin);
1110		BRect textRect(frame.OffsetToCopy(B_ORIGIN));
1111
1112		fText = new BPrivate::_BTextInput_(frame, textRect,
1113			B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS
1114			| (Flags() & B_NAVIGABLE));
1115		AddChild(fText);
1116
1117		SetText(initialText);
1118		fText->SetAlignment(B_ALIGN_LEFT);
1119	}
1120
1121	// Although this is not strictly initializing the text view,
1122	// it cannot be done while fText is NULL, so it resides here.
1123	if (archive) {
1124		int32 labelAlignment = B_ALIGN_LEFT;
1125		int32 textAlignment = B_ALIGN_LEFT;
1126
1127		status_t err = B_OK;
1128		if (archive->HasInt32("_a_label"))
1129			err = archive->FindInt32("_a_label", &labelAlignment);
1130
1131		if (err == B_OK && archive->HasInt32("_a_text"))
1132			err = archive->FindInt32("_a_text", &textAlignment);
1133
1134		SetAlignment((alignment)labelAlignment, (alignment)textAlignment);
1135	}
1136
1137	uint32 navigableFlags = Flags() & B_NAVIGABLE;
1138	if (navigableFlags != 0)
1139		BView::SetFlags(Flags() & ~B_NAVIGABLE);
1140}
1141
1142
1143void
1144BTextControl::_ValidateLayout()
1145{
1146	CALLED();
1147
1148	_ValidateLayoutData();
1149
1150	ResizeTo(Bounds().Width(), fLayoutData->min.height);
1151
1152	_LayoutTextView();
1153}
1154
1155
1156void
1157BTextControl::_LayoutTextView()
1158{
1159	CALLED();
1160
1161	BRect frame;
1162	if (fLayoutData->text_view_layout_item != NULL) {
1163		frame = fLayoutData->text_view_layout_item->FrameInParent();
1164	} else {
1165		frame = Bounds();
1166		frame.left = fDivider;
1167	}
1168
1169	// we are stroking the frame around the text view, which
1170	// is 2 pixels wide
1171	frame.InsetBy(kFrameMargin, kFrameMargin);
1172	fText->MoveTo(frame.left, frame.top);
1173	fText->ResizeTo(frame.Width(), frame.Height());
1174
1175	TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height());
1176	TRACE("fDivider: %.2f\n", fDivider);
1177	TRACE("fText frame: (%.2f, %.2f, %.2f, %.2f)\n",
1178		frame.left, frame.top, frame.right, frame.bottom);
1179}
1180
1181
1182void
1183BTextControl::_UpdateFrame()
1184{
1185	CALLED();
1186
1187	if (fLayoutData->text_view_layout_item != NULL) {
1188		BRect textFrame = fLayoutData->text_view_layout_item->Frame();
1189		BRect labelFrame;
1190		if (fLayoutData->label_layout_item != NULL)
1191			labelFrame = fLayoutData->label_layout_item->Frame();
1192
1193		BRect frame;
1194		if (labelFrame.IsValid()) {
1195			frame = textFrame | labelFrame;
1196
1197			// update divider
1198			fDivider = fabs(textFrame.left - labelFrame.left);
1199		} else {
1200			frame = textFrame;
1201			fDivider = 0;
1202		}
1203
1204		// update our frame
1205		MoveTo(frame.left, frame.top);
1206		BSize oldSize(Bounds().Size());
1207		ResizeTo(frame.Width(), frame.Height());
1208		BSize newSize(Bounds().Size());
1209
1210		// If the size changes, ResizeTo() will trigger a relayout, otherwise
1211		// we need to do that explicitly.
1212		if (newSize != oldSize)
1213			Relayout();
1214	}
1215}
1216
1217
1218void
1219BTextControl::_ValidateLayoutData()
1220{
1221	CALLED();
1222
1223	if (fLayoutData->valid)
1224		return;
1225
1226	// cache font height
1227	font_height& fh = fLayoutData->font_info;
1228	GetFontHeight(&fh);
1229
1230	const char* label = Label();
1231	if (label != NULL) {
1232		fLayoutData->label_width = ceilf(StringWidth(label));
1233		fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent);
1234	} else {
1235		fLayoutData->label_width = 0;
1236		fLayoutData->label_height = 0;
1237	}
1238
1239	// compute the minimal divider
1240	float divider = 0;
1241	if (fLayoutData->label_width > 0) {
1242		divider = fLayoutData->label_width
1243			+ be_control_look->DefaultLabelSpacing();
1244	}
1245
1246	// If we shan't do real layout, we let the current divider take influence.
1247	if (!(Flags() & B_SUPPORTS_LAYOUT))
1248		divider = max_c(divider, fDivider);
1249
1250	// get the minimal (== preferred) text view size
1251	fLayoutData->text_view_min = fText->MinSize();
1252
1253	TRACE("text view min width: %.2f\n", fLayoutData->text_view_min.width);
1254
1255	// compute our minimal (== preferred) size
1256	BSize min(fLayoutData->text_view_min);
1257	min.width += 2 * kFrameMargin;
1258	min.height += 2 * kFrameMargin;
1259
1260	if (divider > 0)
1261		min.width += divider;
1262
1263	if (fLayoutData->label_height > min.height)
1264		min.height = fLayoutData->label_height;
1265
1266	fLayoutData->min = min;
1267
1268	fLayoutData->valid = true;
1269	ResetLayoutInvalidation();
1270
1271	TRACE("width: %.2f, height: %.2f\n", min.width, min.height);
1272}
1273
1274
1275// #pragma mark - BTextControl::LabelLayoutItem
1276
1277
1278BTextControl::LabelLayoutItem::LabelLayoutItem(BTextControl* parent)
1279	:
1280	fParent(parent),
1281	fFrame()
1282{
1283}
1284
1285
1286BTextControl::LabelLayoutItem::LabelLayoutItem(BMessage* from)
1287	:
1288	BAbstractLayoutItem(from),
1289	fParent(NULL),
1290	fFrame()
1291{
1292	from->FindRect(kFrameField, &fFrame);
1293}
1294
1295
1296bool
1297BTextControl::LabelLayoutItem::IsVisible()
1298{
1299	return !fParent->IsHidden(fParent);
1300}
1301
1302
1303void
1304BTextControl::LabelLayoutItem::SetVisible(bool visible)
1305{
1306	// not allowed
1307}
1308
1309
1310BRect
1311BTextControl::LabelLayoutItem::Frame()
1312{
1313	return fFrame;
1314}
1315
1316
1317void
1318BTextControl::LabelLayoutItem::SetFrame(BRect frame)
1319{
1320	fFrame = frame;
1321	fParent->_UpdateFrame();
1322}
1323
1324
1325void
1326BTextControl::LabelLayoutItem::SetParent(BTextControl* parent)
1327{
1328	fParent = parent;
1329}
1330
1331
1332BView*
1333BTextControl::LabelLayoutItem::View()
1334{
1335	return fParent;
1336}
1337
1338
1339BSize
1340BTextControl::LabelLayoutItem::BaseMinSize()
1341{
1342	fParent->_ValidateLayoutData();
1343
1344	if (!fParent->Label())
1345		return BSize(-1, -1);
1346
1347	return BSize(fParent->fLayoutData->label_width
1348			+ be_control_look->DefaultLabelSpacing(),
1349		fParent->fLayoutData->label_height);
1350}
1351
1352
1353BSize
1354BTextControl::LabelLayoutItem::BaseMaxSize()
1355{
1356	return BaseMinSize();
1357}
1358
1359
1360BSize
1361BTextControl::LabelLayoutItem::BasePreferredSize()
1362{
1363	return BaseMinSize();
1364}
1365
1366
1367BAlignment
1368BTextControl::LabelLayoutItem::BaseAlignment()
1369{
1370	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1371}
1372
1373
1374BRect
1375BTextControl::LabelLayoutItem::FrameInParent() const
1376{
1377	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
1378}
1379
1380
1381status_t
1382BTextControl::LabelLayoutItem::Archive(BMessage* into, bool deep) const
1383{
1384	BArchiver archiver(into);
1385	status_t err = BAbstractLayoutItem::Archive(into, deep);
1386	if (err == B_OK)
1387		err = into->AddRect(kFrameField, fFrame);
1388
1389	return archiver.Finish(err);
1390}
1391
1392
1393BArchivable*
1394BTextControl::LabelLayoutItem::Instantiate(BMessage* from)
1395{
1396	if (validate_instantiation(from, "BTextControl::LabelLayoutItem"))
1397		return new LabelLayoutItem(from);
1398	return NULL;
1399}
1400
1401
1402// #pragma mark - BTextControl::TextViewLayoutItem
1403
1404
1405BTextControl::TextViewLayoutItem::TextViewLayoutItem(BTextControl* parent)
1406	:
1407	fParent(parent),
1408	fFrame()
1409{
1410	// by default the part right of the divider shall have an unlimited maximum
1411	// width
1412	SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
1413}
1414
1415
1416BTextControl::TextViewLayoutItem::TextViewLayoutItem(BMessage* from)
1417	:
1418	BAbstractLayoutItem(from),
1419	fParent(NULL),
1420	fFrame()
1421{
1422	from->FindRect(kFrameField, &fFrame);
1423}
1424
1425
1426bool
1427BTextControl::TextViewLayoutItem::IsVisible()
1428{
1429	return !fParent->IsHidden(fParent);
1430}
1431
1432
1433void
1434BTextControl::TextViewLayoutItem::SetVisible(bool visible)
1435{
1436	// not allowed
1437}
1438
1439
1440BRect
1441BTextControl::TextViewLayoutItem::Frame()
1442{
1443	return fFrame;
1444}
1445
1446
1447void
1448BTextControl::TextViewLayoutItem::SetFrame(BRect frame)
1449{
1450	fFrame = frame;
1451	fParent->_UpdateFrame();
1452}
1453
1454
1455void
1456BTextControl::TextViewLayoutItem::SetParent(BTextControl* parent)
1457{
1458	fParent = parent;
1459}
1460
1461
1462BView*
1463BTextControl::TextViewLayoutItem::View()
1464{
1465	return fParent;
1466}
1467
1468
1469BSize
1470BTextControl::TextViewLayoutItem::BaseMinSize()
1471{
1472	fParent->_ValidateLayoutData();
1473
1474	BSize size = fParent->fLayoutData->text_view_min;
1475	size.width += 2 * kFrameMargin;
1476	size.height += 2 * kFrameMargin;
1477
1478	return size;
1479}
1480
1481
1482BSize
1483BTextControl::TextViewLayoutItem::BaseMaxSize()
1484{
1485	BSize size(BaseMinSize());
1486	size.width = B_SIZE_UNLIMITED;
1487
1488	return size;
1489}
1490
1491
1492BSize
1493BTextControl::TextViewLayoutItem::BasePreferredSize()
1494{
1495	BSize size(BaseMinSize());
1496	// puh, no idea...
1497	size.width = 100;
1498
1499	return size;
1500}
1501
1502
1503BAlignment
1504BTextControl::TextViewLayoutItem::BaseAlignment()
1505{
1506	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1507}
1508
1509
1510BRect
1511BTextControl::TextViewLayoutItem::FrameInParent() const
1512{
1513	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
1514}
1515
1516
1517status_t
1518BTextControl::TextViewLayoutItem::Archive(BMessage* into, bool deep) const
1519{
1520	BArchiver archiver(into);
1521	status_t err = BAbstractLayoutItem::Archive(into, deep);
1522	if (err == B_OK)
1523		err = into->AddRect(kFrameField, fFrame);
1524
1525	return archiver.Finish(err);
1526}
1527
1528
1529BArchivable*
1530BTextControl::TextViewLayoutItem::Instantiate(BMessage* from)
1531{
1532	if (validate_instantiation(from, "BTextControl::TextViewLayoutItem"))
1533		return new TextViewLayoutItem(from);
1534
1535	return NULL;
1536}
1537
1538
1539extern "C" void
1540B_IF_GCC_2(InvalidateLayout__12BTextControlb,
1541	_ZN12BTextControl16InvalidateLayoutEb)(BView* view, bool descendants)
1542{
1543	perform_data_layout_invalidated data;
1544	data.descendants = descendants;
1545
1546	view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);
1547}
1548