1/*
2 * Copyright 2001-2015, Haiku Inc.
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 */
10
11
12/*!	BTextControl displays text that can act like a control. */
13
14
15#include <TextControl.h>
16
17#include <string.h>
18
19#include <AbstractLayoutItem.h>
20#include <ControlLook.h>
21#include <LayoutUtils.h>
22#include <Message.h>
23#include <PropertyInfo.h>
24#include <Region.h>
25#include <Window.h>
26
27#include <binary_compatibility/Interface.h>
28#include <binary_compatibility/Support.h>
29
30#include "TextInput.h"
31
32
33//#define TRACE_TEXT_CONTROL
34#ifdef TRACE_TEXT_CONTROL
35#	include <stdio.h>
36#	include <FunctionTracer.h>
37	static int32 sFunctionDepth = -1;
38#	define CALLED(x...)	FunctionTracer _ft("BTextControl", __FUNCTION__, \
39							sFunctionDepth)
40#	define TRACE(x...)	{ BString _to; \
41							_to.Append(' ', (sFunctionDepth + 1) * 2); \
42							printf("%s", _to.String()); printf(x); }
43#else
44#	define CALLED(x...)
45#	define TRACE(x...)
46#endif
47
48
49namespace {
50	const char* const kFrameField = "BTextControl:layoutitem:frame";
51	const char* const kTextViewItemField = "BTextControl:textViewItem";
52	const char* const kLabelItemField = "BMenuField:labelItem";
53}
54
55
56static property_info sPropertyList[] = {
57	{
58		"Value",
59		{ B_GET_PROPERTY, B_SET_PROPERTY },
60		{ B_DIRECT_SPECIFIER },
61		NULL, 0,
62		{ B_STRING_TYPE }
63	},
64
65	{ 0 }
66};
67
68
69class BTextControl::LabelLayoutItem : public BAbstractLayoutItem {
70public:
71								LabelLayoutItem(BTextControl* parent);
72								LabelLayoutItem(BMessage* from);
73
74	virtual	bool				IsVisible();
75	virtual	void				SetVisible(bool visible);
76
77	virtual	BRect				Frame();
78	virtual	void				SetFrame(BRect frame);
79
80			void				SetParent(BTextControl* parent);
81	virtual	BView*				View();
82
83	virtual	BSize				BaseMinSize();
84	virtual	BSize				BaseMaxSize();
85	virtual	BSize				BasePreferredSize();
86	virtual	BAlignment			BaseAlignment();
87
88			BRect				FrameInParent() const;
89
90	virtual status_t			Archive(BMessage* into, bool deep = true) const;
91	static	BArchivable*		Instantiate(BMessage* from);
92
93private:
94			BTextControl*		fParent;
95			BRect				fFrame;
96};
97
98
99class BTextControl::TextViewLayoutItem : public BAbstractLayoutItem {
100public:
101								TextViewLayoutItem(BTextControl* parent);
102								TextViewLayoutItem(BMessage* from);
103
104	virtual	bool				IsVisible();
105	virtual	void				SetVisible(bool visible);
106
107	virtual	BRect				Frame();
108	virtual	void				SetFrame(BRect frame);
109
110			void				SetParent(BTextControl* parent);
111	virtual	BView*				View();
112
113	virtual	BSize				BaseMinSize();
114	virtual	BSize				BaseMaxSize();
115	virtual	BSize				BasePreferredSize();
116	virtual	BAlignment			BaseAlignment();
117
118			BRect				FrameInParent() const;
119
120	virtual status_t			Archive(BMessage* into, bool deep = true) const;
121	static	BArchivable*		Instantiate(BMessage* from);
122private:
123			BTextControl*		fParent;
124			BRect				fFrame;
125};
126
127
128struct BTextControl::LayoutData {
129	LayoutData(float width, float height)
130		:
131		label_layout_item(NULL),
132		text_view_layout_item(NULL),
133		previous_width(width),
134		previous_height(height),
135		valid(false)
136	{
137	}
138
139	LabelLayoutItem*	label_layout_item;
140	TextViewLayoutItem*	text_view_layout_item;
141	float				previous_width;		// used in FrameResized() for
142	float				previous_height;	// invalidation
143	font_height			font_info;
144	float				label_width;
145	float				label_height;
146	BSize				min;
147	BSize				text_view_min;
148	bool				valid;
149};
150
151
152static const int32 kFrameMargin = 2;
153static const int32 kLabelInputSpacing = 3;
154
155
156// #pragma mark - BTextControl
157
158
159BTextControl::BTextControl(BRect frame, const char* name, const char* label,
160	const char* text, BMessage* message, uint32 resizeMask, uint32 flags)
161	:
162	BControl(frame, name, label, message, resizeMask, flags | B_FRAME_EVENTS)
163{
164	_InitData(label);
165	_InitText(text);
166	_ValidateLayout();
167}
168
169
170BTextControl::BTextControl(const char* name, const char* label,
171	const char* text, BMessage* message, uint32 flags)
172	:
173	BControl(name, label, message, flags | B_FRAME_EVENTS)
174{
175	_InitData(label);
176	_InitText(text);
177	_ValidateLayout();
178}
179
180
181BTextControl::BTextControl(const char* label, const char* text,
182	BMessage* message)
183	:
184	BControl(NULL, label, message,
185		B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS)
186{
187	_InitData(label);
188	_InitText(text);
189	_ValidateLayout();
190}
191
192
193BTextControl::~BTextControl()
194{
195	SetModificationMessage(NULL);
196	delete fLayoutData;
197}
198
199
200//	#pragma mark - Archiving
201
202
203BTextControl::BTextControl(BMessage* archive)
204	:
205	BControl(BUnarchiver::PrepareArchive(archive))
206{
207	BUnarchiver unarchiver(archive);
208
209	_InitData(Label(), archive);
210
211	if (!BUnarchiver::IsArchiveManaged(archive))
212		_InitText(NULL, archive);
213
214	status_t err = B_OK;
215	if (archive->HasFloat("_divide"))
216		err = archive->FindFloat("_divide", &fDivider);
217
218	if (err == B_OK && archive->HasMessage("_mod_msg")) {
219		BMessage* message = new BMessage;
220		err = archive->FindMessage("_mod_msg", message);
221		SetModificationMessage(message);
222	}
223
224	unarchiver.Finish(err);
225}
226
227
228BArchivable*
229BTextControl::Instantiate(BMessage* archive)
230{
231	if (validate_instantiation(archive, "BTextControl"))
232		return new BTextControl(archive);
233
234	return NULL;
235}
236
237
238status_t
239BTextControl::Archive(BMessage* data, bool deep) const
240{
241	BArchiver archiver(data);
242	status_t result = BControl::Archive(data, deep);
243
244	alignment labelAlignment;
245	alignment textAlignment;
246	if (result == B_OK)
247		GetAlignment(&labelAlignment, &textAlignment);
248
249	if (result == B_OK)
250		result = data->AddInt32("_a_label", labelAlignment);
251
252	if (result == B_OK)
253		result = data->AddInt32("_a_text", textAlignment);
254
255	if (result == B_OK)
256		result = data->AddFloat("_divide", Divider());
257
258	if (result == B_OK && ModificationMessage() != NULL)
259		result = data->AddMessage("_mod_msg", ModificationMessage());
260
261	return archiver.Finish(result);
262}
263
264
265status_t
266BTextControl::AllArchived(BMessage* into) const
267{
268	BArchiver archiver(into);
269	status_t err = B_OK;
270
271	if (archiver.IsArchived(fLayoutData->text_view_layout_item)) {
272		err = archiver.AddArchivable(kTextViewItemField,
273			fLayoutData->text_view_layout_item);
274	}
275
276	if (err == B_OK && archiver.IsArchived(fLayoutData->label_layout_item)) {
277		err = archiver.AddArchivable(kLabelItemField,
278			fLayoutData->label_layout_item);
279	}
280
281	return err;
282}
283
284
285status_t
286BTextControl::AllUnarchived(const BMessage* from)
287{
288	status_t err;
289	if ((err = BControl::AllUnarchived(from)) != B_OK)
290		return err;
291
292	_InitText(NULL, from);
293
294	BUnarchiver unarchiver(from);
295	if (unarchiver.IsInstantiated(kTextViewItemField)) {
296		err = unarchiver.FindObject(kTextViewItemField,
297			BUnarchiver::B_DONT_ASSUME_OWNERSHIP,
298			fLayoutData->text_view_layout_item);
299
300		if (err == B_OK)
301			fLayoutData->text_view_layout_item->SetParent(this);
302		else
303			return err;
304	}
305
306	if (unarchiver.IsInstantiated(kLabelItemField)) {
307		err = unarchiver.FindObject(kLabelItemField,
308			BUnarchiver::B_DONT_ASSUME_OWNERSHIP,
309			fLayoutData->label_layout_item);
310
311		if (err == B_OK)
312			fLayoutData->label_layout_item->SetParent(this);
313	}
314	return err;
315}
316
317
318//	#pragma mark - Hook methods
319
320
321void
322BTextControl::AllAttached()
323{
324	BControl::AllAttached();
325}
326
327
328void
329BTextControl::AllDetached()
330{
331	BControl::AllDetached();
332}
333
334
335void
336BTextControl::AttachedToWindow()
337{
338	BControl::AttachedToWindow();
339
340	_UpdateTextViewColors(IsEnabled());
341	fText->MakeEditable(IsEnabled());
342}
343
344
345void
346BTextControl::DetachedFromWindow()
347{
348	BControl::DetachedFromWindow();
349}
350
351
352void
353BTextControl::Draw(BRect updateRect)
354{
355	bool enabled = IsEnabled();
356	bool active = fText->IsFocus() && Window()->IsActive();
357
358	BRect rect = fText->Frame();
359	rect.InsetBy(-2, -2);
360
361	rgb_color base = ViewColor();
362	uint32 flags = fLook;
363	if (!enabled)
364		flags |= BControlLook::B_DISABLED;
365
366	if (active)
367		flags |= BControlLook::B_FOCUSED;
368
369	be_control_look->DrawTextControlBorder(this, rect, updateRect, base,
370		flags);
371
372	if (Label() != NULL) {
373		if (fLayoutData->label_layout_item != NULL) {
374			rect = fLayoutData->label_layout_item->FrameInParent();
375		} else {
376			rect = Bounds();
377			rect.right = fDivider - kLabelInputSpacing;
378		}
379
380		// erase the is control flag before drawing the label so that the label
381		// will get drawn using B_PANEL_TEXT_COLOR
382		flags &= ~BControlLook::B_IS_CONTROL;
383
384		be_control_look->DrawLabel(this, Label(), rect, updateRect,
385			base, flags, BAlignment(fLabelAlign, B_ALIGN_MIDDLE));
386	}
387}
388
389
390void
391BTextControl::FrameMoved(BPoint newPosition)
392{
393	BControl::FrameMoved(newPosition);
394}
395
396
397void
398BTextControl::FrameResized(float width, float height)
399{
400	CALLED();
401
402	BControl::FrameResized(width, height);
403
404	// TODO: this causes flickering still...
405
406	// changes in width
407
408	BRect bounds = Bounds();
409
410	if (bounds.Width() > fLayoutData->previous_width) {
411		// invalidate the region between the old and the new right border
412		BRect rect = bounds;
413		rect.left += fLayoutData->previous_width - kFrameMargin;
414		rect.right--;
415		Invalidate(rect);
416	} else if (bounds.Width() < fLayoutData->previous_width) {
417		// invalidate the region of the new right border
418		BRect rect = bounds;
419		rect.left = rect.right - kFrameMargin;
420		Invalidate(rect);
421	}
422
423	// changes in height
424
425	if (bounds.Height() > fLayoutData->previous_height) {
426		// invalidate the region between the old and the new bottom border
427		BRect rect = bounds;
428		rect.top += fLayoutData->previous_height - kFrameMargin;
429		rect.bottom--;
430		Invalidate(rect);
431		// invalidate label area
432		rect = bounds;
433		rect.right = fDivider;
434		Invalidate(rect);
435	} else if (bounds.Height() < fLayoutData->previous_height) {
436		// invalidate the region of the new bottom border
437		BRect rect = bounds;
438		rect.top = rect.bottom - kFrameMargin;
439		Invalidate(rect);
440		// invalidate label area
441		rect = bounds;
442		rect.right = fDivider;
443		Invalidate(rect);
444	}
445
446	fLayoutData->previous_width = bounds.Width();
447	fLayoutData->previous_height = bounds.Height();
448
449	TRACE("width: %.2f, height: %.2f\n", bounds.Width(), bounds.Height());
450}
451
452
453status_t
454BTextControl::Invoke(BMessage* message)
455{
456	return BControl::Invoke(message);
457}
458
459
460void
461BTextControl::LayoutInvalidated(bool descendants)
462{
463	CALLED();
464
465	fLayoutData->valid = false;
466}
467
468
469void
470BTextControl::MessageReceived(BMessage* message)
471{
472	if (message->what == B_COLORS_UPDATED) {
473
474		if (message->HasColor(ui_color_name(B_PANEL_BACKGROUND_COLOR))
475			|| message->HasColor(ui_color_name(B_PANEL_TEXT_COLOR))
476			|| message->HasColor(ui_color_name(B_DOCUMENT_BACKGROUND_COLOR))
477			|| message->HasColor(ui_color_name(B_DOCUMENT_TEXT_COLOR))) {
478			_UpdateTextViewColors(IsEnabled());
479		}
480	}
481
482	if (message->what == B_GET_PROPERTY || message->what == B_SET_PROPERTY) {
483		BMessage reply(B_REPLY);
484		bool handled = false;
485
486		BMessage specifier;
487		int32 index;
488		int32 form;
489		const char* property;
490		if (message->GetCurrentSpecifier(&index, &specifier, &form, &property) == B_OK) {
491			if (strcmp(property, "Value") == 0) {
492				if (message->what == B_GET_PROPERTY) {
493					reply.AddString("result", fText->Text());
494					handled = true;
495				} else {
496					const char* value = NULL;
497					// B_SET_PROPERTY
498					if (message->FindString("data", &value) == B_OK) {
499						fText->SetText(value);
500						reply.AddInt32("error", B_OK);
501						handled = true;
502					}
503				}
504			}
505		}
506
507		if (handled) {
508			message->SendReply(&reply);
509			return;
510		}
511	}
512
513	BControl::MessageReceived(message);
514}
515
516
517void
518BTextControl::MouseDown(BPoint where)
519{
520	if (!fText->IsFocus())
521		fText->MakeFocus(true);
522}
523
524
525void
526BTextControl::MouseMoved(BPoint where, uint32 transit,
527	const BMessage* dragMessage)
528{
529	BControl::MouseMoved(where, transit, dragMessage);
530}
531
532
533void
534BTextControl::MouseUp(BPoint where)
535{
536	BControl::MouseUp(where);
537}
538
539
540void
541BTextControl::WindowActivated(bool active)
542{
543	if (fText->IsFocus()) {
544		// invalidate to remove/show focus indication
545		BRect rect = fText->Frame();
546		rect.InsetBy(-1, -1);
547		Invalidate(rect);
548
549		// help out embedded text view which doesn't
550		// get notified of this
551		fText->Invalidate();
552	}
553}
554
555
556//	#pragma mark - Getters and Setters
557
558
559void
560BTextControl::SetText(const char* text)
561{
562	if (InvokeKind() != B_CONTROL_INVOKED)
563		return;
564
565	CALLED();
566
567	fText->SetText(text);
568
569	if (fText->IsFocus()) {
570		fText->SetInitialText();
571		fText->SelectAll();
572	}
573
574	fText->Invalidate();
575}
576
577
578const char*
579BTextControl::Text() const
580{
581	return fText->Text();
582}
583
584
585int32
586BTextControl::TextLength() const
587{
588	return fText->TextLength();
589}
590
591
592void
593BTextControl::MarkAsInvalid(bool invalid)
594{
595	uint32 look = fLook;
596
597	if (invalid)
598		fLook |= BControlLook::B_INVALID;
599	else
600		fLook &= ~BControlLook::B_INVALID;
601
602	if (look != fLook)
603		Invalidate();
604}
605
606
607void
608BTextControl::SetValue(int32 value)
609{
610	BControl::SetValue(value);
611}
612
613
614BTextView*
615BTextControl::TextView() const
616{
617	return fText;
618}
619
620
621void
622BTextControl::SetModificationMessage(BMessage* message)
623{
624	delete fModificationMessage;
625	fModificationMessage = message;
626}
627
628
629BMessage*
630BTextControl::ModificationMessage() const
631{
632	return fModificationMessage;
633}
634
635
636void
637BTextControl::SetAlignment(alignment labelAlignment, alignment textAlignment)
638{
639	fText->SetAlignment(textAlignment);
640	fText->AlignTextRect();
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
915	fDivider = divider;
916
917	// invalidate dirty region
918	dirty = dirty | fText->Frame();
919	dirty.InsetBy(-kFrameMargin, -kFrameMargin);
920
921	Invalidate(dirty);
922}
923
924
925// #pragma mark - protected methods
926
927
928status_t
929BTextControl::SetIcon(const BBitmap* icon, uint32 flags)
930{
931	return BControl::SetIcon(icon, flags);
932}
933
934
935// #pragma mark - private methods
936
937
938status_t
939BTextControl::Perform(perform_code code, void* _data)
940{
941	switch (code) {
942		case PERFORM_CODE_MIN_SIZE:
943			((perform_data_min_size*)_data)->return_value
944				= BTextControl::MinSize();
945			return B_OK;
946
947		case PERFORM_CODE_MAX_SIZE:
948			((perform_data_max_size*)_data)->return_value
949				= BTextControl::MaxSize();
950			return B_OK;
951
952		case PERFORM_CODE_PREFERRED_SIZE:
953			((perform_data_preferred_size*)_data)->return_value
954				= BTextControl::PreferredSize();
955			return B_OK;
956
957		case PERFORM_CODE_LAYOUT_ALIGNMENT:
958			((perform_data_layout_alignment*)_data)->return_value
959				= BTextControl::LayoutAlignment();
960			return B_OK;
961
962		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
963			((perform_data_has_height_for_width*)_data)->return_value
964				= BTextControl::HasHeightForWidth();
965			return B_OK;
966
967		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
968		{
969			perform_data_get_height_for_width* data
970				= (perform_data_get_height_for_width*)_data;
971			BTextControl::GetHeightForWidth(data->width, &data->min, &data->max,
972				&data->preferred);
973			return B_OK;
974		}
975
976		case PERFORM_CODE_SET_LAYOUT:
977		{
978			perform_data_set_layout* data = (perform_data_set_layout*)_data;
979			BTextControl::SetLayout(data->layout);
980			return B_OK;
981		}
982
983		case PERFORM_CODE_LAYOUT_INVALIDATED:
984		{
985			perform_data_layout_invalidated* data
986				= (perform_data_layout_invalidated*)_data;
987			BTextControl::LayoutInvalidated(data->descendants);
988			return B_OK;
989		}
990
991		case PERFORM_CODE_DO_LAYOUT:
992		{
993			BTextControl::DoLayout();
994			return B_OK;
995		}
996
997		case PERFORM_CODE_SET_ICON:
998		{
999			perform_data_set_icon* data = (perform_data_set_icon*)_data;
1000			return BTextControl::SetIcon(data->icon, data->flags);
1001		}
1002
1003		case PERFORM_CODE_ALL_UNARCHIVED:
1004		{
1005			perform_data_all_unarchived* data
1006				= (perform_data_all_unarchived*)_data;
1007			data->return_value = BTextControl::AllUnarchived(data->archive);
1008			return B_OK;
1009		}
1010
1011		case PERFORM_CODE_ALL_ARCHIVED:
1012		{
1013			perform_data_all_archived* data
1014				= (perform_data_all_archived*)_data;
1015			data->return_value = BTextControl::AllArchived(data->archive);
1016			return B_OK;
1017		}
1018	}
1019
1020	return BControl::Perform(code, _data);
1021}
1022
1023
1024//	#pragma mark - FBC padding
1025
1026
1027void BTextControl::_ReservedTextControl1() {}
1028void BTextControl::_ReservedTextControl2() {}
1029void BTextControl::_ReservedTextControl3() {}
1030void BTextControl::_ReservedTextControl4() {}
1031
1032
1033BTextControl&
1034BTextControl::operator=(const BTextControl&)
1035{
1036	return *this;
1037}
1038
1039
1040void
1041BTextControl::_UpdateTextViewColors(bool enable)
1042{
1043	rgb_color textColor = ui_color(B_DOCUMENT_TEXT_COLOR);
1044	rgb_color viewColor = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
1045	BFont font;
1046
1047	fText->GetFontAndColor(0, &font);
1048
1049	if (!enable) {
1050		textColor = disable_color(textColor, ViewColor());
1051		viewColor = disable_color(ViewColor(), viewColor);
1052	}
1053
1054	fText->SetFontAndColor(&font, B_FONT_ALL, &textColor);
1055	fText->SetViewColor(viewColor);
1056	fText->SetLowColor(viewColor);
1057}
1058
1059
1060void
1061BTextControl::_CommitValue()
1062{
1063}
1064
1065
1066void
1067BTextControl::_InitData(const char* label, const BMessage* archive)
1068{
1069	BRect bounds(Bounds());
1070
1071	fText = NULL;
1072	fModificationMessage = NULL;
1073	fLabelAlign = B_ALIGN_LEFT;
1074	fDivider = 0.0f;
1075	fLayoutData = new LayoutData(bounds.Width(), bounds.Height());
1076
1077	int32 flags = 0;
1078
1079	BFont font(be_plain_font);
1080
1081	if (!archive || !archive->HasString("_fname"))
1082		flags |= B_FONT_FAMILY_AND_STYLE;
1083
1084	if (!archive || !archive->HasFloat("_fflt"))
1085		flags |= B_FONT_SIZE;
1086
1087	if (flags != 0)
1088		SetFont(&font, flags);
1089
1090	if (label != NULL)
1091		fDivider = floorf(bounds.Width() / 2.0f);
1092
1093	fLook = 0;
1094}
1095
1096
1097void
1098BTextControl::_InitText(const char* initialText, const BMessage* archive)
1099{
1100	if (archive)
1101		fText = static_cast<BPrivate::_BTextInput_*>(FindView("_input_"));
1102
1103	if (fText == NULL) {
1104		BRect bounds(Bounds());
1105		BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom);
1106		// we are stroking the frame around the text view, which
1107		// is 2 pixels wide
1108		frame.InsetBy(kFrameMargin, kFrameMargin);
1109		BRect textRect(frame.OffsetToCopy(B_ORIGIN));
1110
1111		fText = new BPrivate::_BTextInput_(frame, textRect,
1112			B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS
1113			| (Flags() & B_NAVIGABLE));
1114		AddChild(fText);
1115
1116		SetText(initialText);
1117		fText->SetAlignment(B_ALIGN_LEFT);
1118		fText->AlignTextRect();
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	fText->AlignTextRect();
1175
1176	TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height());
1177	TRACE("fDivider: %.2f\n", fDivider);
1178	TRACE("fText frame: (%.2f, %.2f, %.2f, %.2f)\n",
1179		frame.left, frame.top, frame.right, frame.bottom);
1180}
1181
1182
1183void
1184BTextControl::_UpdateFrame()
1185{
1186	CALLED();
1187
1188	if (fLayoutData->text_view_layout_item != NULL) {
1189		BRect textFrame = fLayoutData->text_view_layout_item->Frame();
1190		BRect labelFrame;
1191		if (fLayoutData->label_layout_item != NULL)
1192			labelFrame = fLayoutData->label_layout_item->Frame();
1193
1194		BRect frame;
1195		if (labelFrame.IsValid()) {
1196			frame = textFrame | labelFrame;
1197
1198			// update divider
1199			fDivider = fabs(textFrame.left - labelFrame.left);
1200		} else {
1201			frame = textFrame;
1202			fDivider = 0;
1203		}
1204
1205		// update our frame
1206		MoveTo(frame.left, frame.top);
1207		BSize oldSize(Bounds().Size());
1208		ResizeTo(frame.Width(), frame.Height());
1209		BSize newSize(Bounds().Size());
1210
1211		// If the size changes, ResizeTo() will trigger a relayout, otherwise
1212		// we need to do that explicitly.
1213		if (newSize != oldSize)
1214			Relayout();
1215	}
1216}
1217
1218
1219void
1220BTextControl::_ValidateLayoutData()
1221{
1222	CALLED();
1223
1224	if (fLayoutData->valid)
1225		return;
1226
1227	// cache font height
1228	font_height& fh = fLayoutData->font_info;
1229	GetFontHeight(&fh);
1230
1231	const char* label = Label();
1232	if (label != NULL) {
1233		fLayoutData->label_width = ceilf(StringWidth(label));
1234		fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent);
1235	} else {
1236		fLayoutData->label_width = 0;
1237		fLayoutData->label_height = 0;
1238	}
1239
1240	// compute the minimal divider
1241	float divider = 0;
1242	if (fLayoutData->label_width > 0) {
1243		divider = fLayoutData->label_width
1244			+ be_control_look->DefaultLabelSpacing();
1245	}
1246
1247	// If we shan't do real layout, we let the current divider take influence.
1248	if (!(Flags() & B_SUPPORTS_LAYOUT))
1249		divider = max_c(divider, fDivider);
1250
1251	// get the minimal (== preferred) text view size
1252	fLayoutData->text_view_min = fText->MinSize();
1253
1254	TRACE("text view min width: %.2f\n", fLayoutData->text_view_min.width);
1255
1256	// compute our minimal (== preferred) size
1257	BSize min(fLayoutData->text_view_min);
1258	min.width += 2 * kFrameMargin;
1259	min.height += 2 * kFrameMargin;
1260
1261	if (divider > 0)
1262		min.width += divider;
1263
1264	if (fLayoutData->label_height > min.height)
1265		min.height = fLayoutData->label_height;
1266
1267	fLayoutData->min = min;
1268
1269	fLayoutData->valid = true;
1270	ResetLayoutInvalidation();
1271
1272	TRACE("width: %.2f, height: %.2f\n", min.width, min.height);
1273}
1274
1275
1276// #pragma mark - BTextControl::LabelLayoutItem
1277
1278
1279BTextControl::LabelLayoutItem::LabelLayoutItem(BTextControl* parent)
1280	:
1281	fParent(parent),
1282	fFrame()
1283{
1284}
1285
1286
1287BTextControl::LabelLayoutItem::LabelLayoutItem(BMessage* from)
1288	:
1289	BAbstractLayoutItem(from),
1290	fParent(NULL),
1291	fFrame()
1292{
1293	from->FindRect(kFrameField, &fFrame);
1294}
1295
1296
1297bool
1298BTextControl::LabelLayoutItem::IsVisible()
1299{
1300	return !fParent->IsHidden(fParent);
1301}
1302
1303
1304void
1305BTextControl::LabelLayoutItem::SetVisible(bool visible)
1306{
1307	// not allowed
1308}
1309
1310
1311BRect
1312BTextControl::LabelLayoutItem::Frame()
1313{
1314	return fFrame;
1315}
1316
1317
1318void
1319BTextControl::LabelLayoutItem::SetFrame(BRect frame)
1320{
1321	fFrame = frame;
1322	fParent->_UpdateFrame();
1323}
1324
1325
1326void
1327BTextControl::LabelLayoutItem::SetParent(BTextControl* parent)
1328{
1329	fParent = parent;
1330}
1331
1332
1333BView*
1334BTextControl::LabelLayoutItem::View()
1335{
1336	return fParent;
1337}
1338
1339
1340BSize
1341BTextControl::LabelLayoutItem::BaseMinSize()
1342{
1343	fParent->_ValidateLayoutData();
1344
1345	if (!fParent->Label())
1346		return BSize(-1, -1);
1347
1348	return BSize(fParent->fLayoutData->label_width
1349			+ be_control_look->DefaultLabelSpacing(),
1350		fParent->fLayoutData->label_height);
1351}
1352
1353
1354BSize
1355BTextControl::LabelLayoutItem::BaseMaxSize()
1356{
1357	return BaseMinSize();
1358}
1359
1360
1361BSize
1362BTextControl::LabelLayoutItem::BasePreferredSize()
1363{
1364	return BaseMinSize();
1365}
1366
1367
1368BAlignment
1369BTextControl::LabelLayoutItem::BaseAlignment()
1370{
1371	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1372}
1373
1374
1375BRect
1376BTextControl::LabelLayoutItem::FrameInParent() const
1377{
1378	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
1379}
1380
1381
1382status_t
1383BTextControl::LabelLayoutItem::Archive(BMessage* into, bool deep) const
1384{
1385	BArchiver archiver(into);
1386	status_t err = BAbstractLayoutItem::Archive(into, deep);
1387	if (err == B_OK)
1388		err = into->AddRect(kFrameField, fFrame);
1389
1390	return archiver.Finish(err);
1391}
1392
1393
1394BArchivable*
1395BTextControl::LabelLayoutItem::Instantiate(BMessage* from)
1396{
1397	if (validate_instantiation(from, "BTextControl::LabelLayoutItem"))
1398		return new LabelLayoutItem(from);
1399	return NULL;
1400}
1401
1402
1403// #pragma mark - BTextControl::TextViewLayoutItem
1404
1405
1406BTextControl::TextViewLayoutItem::TextViewLayoutItem(BTextControl* parent)
1407	:
1408	fParent(parent),
1409	fFrame()
1410{
1411	// by default the part right of the divider shall have an unlimited maximum
1412	// width
1413	SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
1414}
1415
1416
1417BTextControl::TextViewLayoutItem::TextViewLayoutItem(BMessage* from)
1418	:
1419	BAbstractLayoutItem(from),
1420	fParent(NULL),
1421	fFrame()
1422{
1423	from->FindRect(kFrameField, &fFrame);
1424}
1425
1426
1427bool
1428BTextControl::TextViewLayoutItem::IsVisible()
1429{
1430	return !fParent->IsHidden(fParent);
1431}
1432
1433
1434void
1435BTextControl::TextViewLayoutItem::SetVisible(bool visible)
1436{
1437	// not allowed
1438}
1439
1440
1441BRect
1442BTextControl::TextViewLayoutItem::Frame()
1443{
1444	return fFrame;
1445}
1446
1447
1448void
1449BTextControl::TextViewLayoutItem::SetFrame(BRect frame)
1450{
1451	fFrame = frame;
1452	fParent->_UpdateFrame();
1453}
1454
1455
1456void
1457BTextControl::TextViewLayoutItem::SetParent(BTextControl* parent)
1458{
1459	fParent = parent;
1460}
1461
1462
1463BView*
1464BTextControl::TextViewLayoutItem::View()
1465{
1466	return fParent;
1467}
1468
1469
1470BSize
1471BTextControl::TextViewLayoutItem::BaseMinSize()
1472{
1473	fParent->_ValidateLayoutData();
1474
1475	BSize size = fParent->fLayoutData->text_view_min;
1476	size.width += 2 * kFrameMargin;
1477	size.height += 2 * kFrameMargin;
1478
1479	return size;
1480}
1481
1482
1483BSize
1484BTextControl::TextViewLayoutItem::BaseMaxSize()
1485{
1486	BSize size(BaseMinSize());
1487	size.width = B_SIZE_UNLIMITED;
1488
1489	return size;
1490}
1491
1492
1493BSize
1494BTextControl::TextViewLayoutItem::BasePreferredSize()
1495{
1496	BSize size(BaseMinSize());
1497	// puh, no idea...
1498	size.width = 100;
1499
1500	return size;
1501}
1502
1503
1504BAlignment
1505BTextControl::TextViewLayoutItem::BaseAlignment()
1506{
1507	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1508}
1509
1510
1511BRect
1512BTextControl::TextViewLayoutItem::FrameInParent() const
1513{
1514	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
1515}
1516
1517
1518status_t
1519BTextControl::TextViewLayoutItem::Archive(BMessage* into, bool deep) const
1520{
1521	BArchiver archiver(into);
1522	status_t err = BAbstractLayoutItem::Archive(into, deep);
1523	if (err == B_OK)
1524		err = into->AddRect(kFrameField, fFrame);
1525
1526	return archiver.Finish(err);
1527}
1528
1529
1530BArchivable*
1531BTextControl::TextViewLayoutItem::Instantiate(BMessage* from)
1532{
1533	if (validate_instantiation(from, "BTextControl::TextViewLayoutItem"))
1534		return new TextViewLayoutItem(from);
1535
1536	return NULL;
1537}
1538
1539
1540extern "C" void
1541B_IF_GCC_2(InvalidateLayout__12BTextControlb,
1542	_ZN12BTextControl16InvalidateLayoutEb)(BView* view, bool descendants)
1543{
1544	perform_data_layout_invalidated data;
1545	data.descendants = descendants;
1546
1547	view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);
1548}
1549