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