xref: /haiku/src/kits/interface/TextControl.cpp (revision 4f2fd49bdc6078128b1391191e4edac647044c3d)
1 /*
2  * Copyright 2001-2008, 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 /*!	BTextControl displays text that can act like a control. */
12 
13 
14 #include <AbstractLayoutItem.h>
15 #include <LayoutUtils.h>
16 #include <Message.h>
17 #include <Region.h>
18 #include <TextControl.h>
19 #include <Window.h>
20 
21 #include <binary_compatibility/Interface.h>
22 
23 #include "TextInput.h"
24 
25 
26 //#define TRACE_TEXT_CONTROL
27 #ifdef TRACE_TEXT_CONTROL
28 #	include <stdio.h>
29 #	include <FunctionTracer.h>
30 	static int32 sFunctionDepth = -1;
31 #	define CALLED(x...)	FunctionTracer _ft("BTextControl", __FUNCTION__, \
32 							sFunctionDepth)
33 #	define TRACE(x...)	{ BString _to; \
34 							_to.Append(' ', (sFunctionDepth + 1) * 2); \
35 							printf("%s", _to.String()); printf(x); }
36 #else
37 #	define CALLED(x...)
38 #	define TRACE(x...)
39 #endif
40 
41 
42 class BTextControl::LabelLayoutItem : public BAbstractLayoutItem {
43 public:
44 								LabelLayoutItem(BTextControl* parent);
45 
46 	virtual	bool				IsVisible();
47 	virtual	void				SetVisible(bool visible);
48 
49 	virtual	BRect				Frame();
50 	virtual	void				SetFrame(BRect frame);
51 
52 	virtual	BView*				View();
53 
54 	virtual	BSize				BaseMinSize();
55 	virtual	BSize				BaseMaxSize();
56 	virtual	BSize				BasePreferredSize();
57 	virtual	BAlignment			BaseAlignment();
58 
59 private:
60 			BTextControl*		fParent;
61 			BRect				fFrame;
62 };
63 
64 
65 class BTextControl::TextViewLayoutItem : public BAbstractLayoutItem {
66 public:
67 								TextViewLayoutItem(BTextControl* parent);
68 
69 	virtual	bool				IsVisible();
70 	virtual	void				SetVisible(bool visible);
71 
72 	virtual	BRect				Frame();
73 	virtual	void				SetFrame(BRect frame);
74 
75 	virtual	BView*				View();
76 
77 	virtual	BSize				BaseMinSize();
78 	virtual	BSize				BaseMaxSize();
79 	virtual	BSize				BasePreferredSize();
80 	virtual	BAlignment			BaseAlignment();
81 
82 private:
83 			BTextControl*		fParent;
84 			BRect				fFrame;
85 };
86 
87 
88 struct BTextControl::LayoutData {
89 	LayoutData(float width, float height)
90 		: label_layout_item(NULL),
91 		  text_view_layout_item(NULL),
92 		  previous_width(width),
93 		  previous_height(height),
94 		  valid(false)
95 	{
96 	}
97 
98 	LabelLayoutItem*	label_layout_item;
99 	TextViewLayoutItem*	text_view_layout_item;
100 	float				previous_width;		// used in FrameResized() for
101 	float				previous_height;	// invalidation
102 	font_height			font_info;
103 	float				label_width;
104 	float				label_height;
105 	BSize				min;
106 	BSize				text_view_min;
107 	bool				valid;
108 };
109 
110 
111 // #pragma mark -
112 
113 
114 static const int32 kFrameMargin = 2;
115 static const int32 kLabelInputSpacing = 3;
116 
117 
118 BTextControl::BTextControl(BRect frame, const char* name, const char* label,
119 		const char* text, BMessage* message, uint32 mask, uint32 flags)
120 	: BControl(frame, name, label, message, mask, flags | B_FRAME_EVENTS)
121 {
122 	_InitData(label, text);
123 	_ValidateLayout();
124 }
125 
126 
127 BTextControl::BTextControl(const char* name, const char* label,
128 		const char* text, BMessage* message, uint32 flags)
129 	: BControl(name, label, message, flags | B_FRAME_EVENTS)
130 {
131 	_InitData(label, text);
132 	_ValidateLayout();
133 }
134 
135 
136 BTextControl::BTextControl(const char* label, const char* text,
137 		BMessage* message)
138 	: BControl(NULL, label, message,
139 		B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS)
140 {
141 	_InitData(label, text);
142 	_ValidateLayout();
143 }
144 
145 
146 BTextControl::~BTextControl()
147 {
148 	SetModificationMessage(NULL);
149 	delete fLayoutData;
150 }
151 
152 
153 BTextControl::BTextControl(BMessage* archive)
154 	: BControl(archive)
155 {
156 	_InitData(Label(), NULL, archive);
157 
158 	int32 labelAlignment = B_ALIGN_LEFT;
159 	int32 textAlignment = B_ALIGN_LEFT;
160 
161 	if (archive->HasInt32("_a_label"))
162 		archive->FindInt32("_a_label", &labelAlignment);
163 
164 	if (archive->HasInt32("_a_text"))
165 		archive->FindInt32("_a_text", &textAlignment);
166 
167 	SetAlignment((alignment)labelAlignment, (alignment)textAlignment);
168 
169 	if (archive->HasFloat("_divide"))
170 		archive->FindFloat("_divide", &fDivider);
171 
172 	if (archive->HasMessage("_mod_msg")) {
173 		BMessage* message = new BMessage;
174 		archive->FindMessage("_mod_msg", message);
175 		SetModificationMessage(message);
176 	}
177 }
178 
179 
180 BArchivable*
181 BTextControl::Instantiate(BMessage* archive)
182 {
183 	if (validate_instantiation(archive, "BTextControl"))
184 		return new BTextControl(archive);
185 
186 	return NULL;
187 }
188 
189 
190 status_t
191 BTextControl::Archive(BMessage *data, bool deep) const
192 {
193 	status_t ret = BControl::Archive(data, deep);
194 	alignment labelAlignment, textAlignment;
195 
196 	GetAlignment(&labelAlignment, &textAlignment);
197 
198 	if (ret == B_OK)
199 		ret = data->AddInt32("_a_label", labelAlignment);
200 	if (ret == B_OK)
201 		ret = data->AddInt32("_a_text", textAlignment);
202 	if (ret == B_OK)
203 		ret = data->AddFloat("_divide", Divider());
204 
205 	if (ModificationMessage() && (ret == B_OK))
206 		ret = data->AddMessage("_mod_msg", ModificationMessage());
207 
208 	return ret;
209 }
210 
211 
212 void
213 BTextControl::SetText(const char *text)
214 {
215 	if (InvokeKind() != B_CONTROL_INVOKED)
216 		return;
217 
218 	CALLED();
219 
220 	fText->SetText(text);
221 
222 	if (IsFocus())
223 		fText->SetInitialText();
224 
225 	fText->Invalidate();
226 }
227 
228 
229 const char *
230 BTextControl::Text() const
231 {
232 	return fText->Text();
233 }
234 
235 
236 void
237 BTextControl::SetValue(int32 value)
238 {
239 	BControl::SetValue(value);
240 }
241 
242 
243 status_t
244 BTextControl::Invoke(BMessage *message)
245 {
246 	return BControl::Invoke(message);
247 }
248 
249 
250 BTextView *
251 BTextControl::TextView() const
252 {
253 	return fText;
254 }
255 
256 
257 void
258 BTextControl::SetModificationMessage(BMessage *message)
259 {
260 	delete fModificationMessage;
261 	fModificationMessage = message;
262 }
263 
264 
265 BMessage *
266 BTextControl::ModificationMessage() const
267 {
268 	return fModificationMessage;
269 }
270 
271 
272 void
273 BTextControl::SetAlignment(alignment labelAlignment, alignment textAlignment)
274 {
275 	fText->SetAlignment(textAlignment);
276 	fText->AlignTextRect();
277 
278 	if (fLabelAlign != labelAlignment) {
279 		fLabelAlign = labelAlignment;
280 		Invalidate();
281 	}
282 }
283 
284 
285 void
286 BTextControl::GetAlignment(alignment* _label, alignment* _text) const
287 {
288 	if (_label)
289 		*_label = fLabelAlign;
290 	if (_text)
291 		*_text = fText->Alignment();
292 }
293 
294 
295 void
296 BTextControl::SetDivider(float dividingLine)
297 {
298 	fDivider = floorf(dividingLine + 0.5);
299 
300 	_LayoutTextView();
301 
302 	if (Window()) {
303 		fText->Invalidate();
304 		Invalidate();
305 	}
306 }
307 
308 
309 float
310 BTextControl::Divider() const
311 {
312 	return fDivider;
313 }
314 
315 
316 void
317 BTextControl::Draw(BRect updateRect)
318 {
319 	rgb_color noTint = ui_color(B_PANEL_BACKGROUND_COLOR);
320 	rgb_color lighten1 = tint_color(noTint, B_LIGHTEN_1_TINT);
321 	rgb_color lighten2 = tint_color(noTint, B_LIGHTEN_2_TINT);
322 	rgb_color lightenMax = tint_color(noTint, B_LIGHTEN_MAX_TINT);
323 	rgb_color darken1 = tint_color(noTint, B_DARKEN_1_TINT);
324 	rgb_color darken2 = tint_color(noTint, B_DARKEN_2_TINT);
325 	rgb_color darken4 = tint_color(noTint, B_DARKEN_4_TINT);
326 	rgb_color navigationColor = ui_color(B_KEYBOARD_NAVIGATION_COLOR);
327 
328 	bool enabled = IsEnabled();
329 	bool active = false;
330 
331 	if (fText->IsFocus() && Window()->IsActive())
332 		active = true;
333 
334 	// outer bevel
335 
336 	BRect rect = fText->Frame();
337 	rect.InsetBy(-2, -2);
338 
339 	if (enabled)
340 		SetHighColor(darken1);
341 	else
342 		SetHighColor(noTint);
343 
344 	StrokeLine(rect.LeftBottom(), rect.LeftTop());
345 	StrokeLine(rect.RightTop());
346 
347 	if (enabled)
348 		SetHighColor(lighten2);
349 	else
350 		SetHighColor(lighten1);
351 
352 	StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom());
353 	StrokeLine(BPoint(rect.right, rect.top + 1.0f), rect.RightBottom());
354 
355 	// inner bevel
356 
357 	rect.InsetBy(1.0f, 1.0f);
358 
359 	if (active) {
360 		SetHighColor(navigationColor);
361 		StrokeRect(rect);
362 	} else {
363 		if (enabled)
364 			SetHighColor(darken4);
365 		else
366 			SetHighColor(darken2);
367 
368 		StrokeLine(rect.LeftTop(), rect.LeftBottom());
369 		StrokeLine(rect.LeftTop(), rect.RightTop());
370 
371 		SetHighColor(noTint);
372 		StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom());
373 		StrokeLine(BPoint(rect.right, rect.top + 1.0f));
374 	}
375 
376 	// label
377 
378 	if (Label()) {
379 		_ValidateLayoutData();
380 		font_height& fontHeight = fLayoutData->font_info;
381 
382 		float y = Bounds().top + (Bounds().Height() + 1 - fontHeight.ascent
383 			- fontHeight.descent) / 2 + fontHeight.ascent;
384 		float x;
385 
386 		float labelWidth = StringWidth(Label());
387 		switch (fLabelAlign) {
388 			case B_ALIGN_RIGHT:
389 				x = fDivider - labelWidth - kLabelInputSpacing;
390 				break;
391 
392 			case B_ALIGN_CENTER:
393 				x = fDivider - labelWidth / 2.0;
394 				break;
395 
396 			default:
397 				x = 0.0;
398 				break;
399 		}
400 
401 		BRect labelArea(x, Bounds().top, x + labelWidth, Bounds().bottom);
402 		if (x < fDivider && updateRect.Intersects(labelArea)) {
403 			labelArea.right = fText->Frame().left - kLabelInputSpacing;
404 
405 			BRegion clipRegion(labelArea);
406 			ConstrainClippingRegion(&clipRegion);
407 			SetHighColor(IsEnabled() ? ui_color(B_CONTROL_TEXT_COLOR)
408 				: tint_color(noTint, B_DISABLED_LABEL_TINT));
409 			DrawString(Label(), BPoint(x, y));
410 		}
411 	}
412 }
413 
414 
415 void
416 BTextControl::MouseDown(BPoint where)
417 {
418 	if (!fText->IsFocus()) {
419 		fText->MakeFocus(true);
420 	}
421 }
422 
423 
424 void
425 BTextControl::AttachedToWindow()
426 {
427 	BControl::AttachedToWindow();
428 
429 	_UpdateTextViewColors(IsEnabled());
430 	fText->MakeEditable(IsEnabled());
431 }
432 
433 
434 void
435 BTextControl::MakeFocus(bool state)
436 {
437 	if (state != fText->IsFocus()) {
438 		fText->MakeFocus(state);
439 
440 		if (state)
441 			fText->SelectAll();
442 	}
443 }
444 
445 
446 void
447 BTextControl::SetEnabled(bool enabled)
448 {
449 	if (IsEnabled() == enabled)
450 		return;
451 
452 	if (Window()) {
453 		fText->MakeEditable(enabled);
454 
455 		_UpdateTextViewColors(enabled);
456 
457 		fText->Invalidate();
458 		Window()->UpdateIfNeeded();
459 	}
460 
461 	BControl::SetEnabled(enabled);
462 }
463 
464 
465 void
466 BTextControl::GetPreferredSize(float *_width, float *_height)
467 {
468 	CALLED();
469 
470 	_ValidateLayoutData();
471 
472 	if (_width) {
473 		float minWidth = fLayoutData->min.width;
474 		if (Label() == NULL && !(Flags() & B_SUPPORTS_LAYOUT)) {
475 			// Indeed, only if there is no label! BeOS backwards compatible
476 			// behavior:
477 			minWidth = max_c(minWidth, Bounds().Width());
478 		}
479 		*_width = minWidth;
480 	}
481 
482 	if (_height)
483 		*_height = fLayoutData->min.height;
484 }
485 
486 
487 void
488 BTextControl::ResizeToPreferred()
489 {
490 	BView::ResizeToPreferred();
491 
492 	fDivider = 0.0;
493 	const char* label = Label();
494 	if (label)
495 		fDivider = ceil(StringWidth(label)) + 2.0;
496 
497 	_LayoutTextView();
498 }
499 
500 
501 void
502 BTextControl::SetFlags(uint32 flags)
503 {
504 	// If the textview is navigable, set it to not navigable if needed
505 	// Else if it is not navigable, set it to navigable if needed
506 	if (fText->Flags() & B_NAVIGABLE) {
507 		if (!(flags & B_NAVIGABLE))
508 			fText->SetFlags(fText->Flags() & ~B_NAVIGABLE);
509 
510 	} else {
511 		if (flags & B_NAVIGABLE)
512 			fText->SetFlags(fText->Flags() | B_NAVIGABLE);
513 	}
514 
515 	BView::SetFlags(flags);
516 }
517 
518 
519 void
520 BTextControl::MessageReceived(BMessage *msg)
521 {
522 	switch(msg->what) {
523 		case B_SET_PROPERTY:
524 		case B_GET_PROPERTY:
525 			// TODO
526 			break;
527 		default:
528 			BControl::MessageReceived(msg);
529 			break;
530 	}
531 }
532 
533 
534 BHandler *
535 BTextControl::ResolveSpecifier(BMessage *msg, int32 index,
536 										 BMessage *specifier, int32 form,
537 										 const char *property)
538 {
539 	/*
540 	BPropertyInfo propInfo(prop_list);
541 	BHandler *target = NULL;
542 
543 	if (propInfo.FindMatch(message, 0, specifier, what, property) < B_OK)
544 		return BControl::ResolveSpecifier(message, index, specifier, what,
545 			property);
546 	else
547 		return this;
548 	*/
549 	return BControl::ResolveSpecifier(msg, index, specifier, form, property);
550 }
551 
552 
553 status_t
554 BTextControl::GetSupportedSuites(BMessage *data)
555 {
556 	return BControl::GetSupportedSuites(data);
557 }
558 
559 
560 void
561 BTextControl::MouseUp(BPoint pt)
562 {
563 	BControl::MouseUp(pt);
564 }
565 
566 
567 void
568 BTextControl::MouseMoved(BPoint pt, uint32 code, const BMessage *msg)
569 {
570 	BControl::MouseMoved(pt, code, msg);
571 }
572 
573 
574 void
575 BTextControl::DetachedFromWindow()
576 {
577 	BControl::DetachedFromWindow();
578 }
579 
580 
581 void
582 BTextControl::AllAttached()
583 {
584 	BControl::AllAttached();
585 }
586 
587 
588 void
589 BTextControl::AllDetached()
590 {
591 	BControl::AllDetached();
592 }
593 
594 
595 void
596 BTextControl::FrameMoved(BPoint newPosition)
597 {
598 	BControl::FrameMoved(newPosition);
599 }
600 
601 
602 void
603 BTextControl::FrameResized(float width, float height)
604 {
605 	CALLED();
606 
607 	BControl::FrameResized(width, height);
608 
609 	// TODO: this causes flickering still...
610 
611 	// changes in width
612 
613 	BRect bounds = Bounds();
614 
615 	if (bounds.Width() > fLayoutData->previous_width) {
616 		// invalidate the region between the old and the new right border
617 		BRect rect = bounds;
618 		rect.left += fLayoutData->previous_width - kFrameMargin;
619 		rect.right--;
620 		Invalidate(rect);
621 	} else if (bounds.Width() < fLayoutData->previous_width) {
622 		// invalidate the region of the new right border
623 		BRect rect = bounds;
624 		rect.left = rect.right - kFrameMargin;
625 		Invalidate(rect);
626 	}
627 
628 	// changes in height
629 
630 	if (bounds.Height() > fLayoutData->previous_height) {
631 		// invalidate the region between the old and the new bottom border
632 		BRect rect = bounds;
633 		rect.top += fLayoutData->previous_height - kFrameMargin;
634 		rect.bottom--;
635 		Invalidate(rect);
636 		// invalidate label area
637 		rect = bounds;
638 		rect.right = fDivider;
639 		Invalidate(rect);
640 	} else if (bounds.Height() < fLayoutData->previous_height) {
641 		// invalidate the region of the new bottom border
642 		BRect rect = bounds;
643 		rect.top = rect.bottom - kFrameMargin;
644 		Invalidate(rect);
645 		// invalidate label area
646 		rect = bounds;
647 		rect.right = fDivider;
648 		Invalidate(rect);
649 	}
650 
651 	fLayoutData->previous_width = bounds.Width();
652 	fLayoutData->previous_height = bounds.Height();
653 
654 	TRACE("width: %.2f, height: %.2f\n", bounds.Width(), bounds.Height());
655 }
656 
657 
658 void
659 BTextControl::WindowActivated(bool active)
660 {
661 	if (fText->IsFocus()) {
662 		// invalidate to remove/show focus indication
663 		BRect rect = fText->Frame();
664 		rect.InsetBy(-1, -1);
665 		Invalidate(rect);
666 
667 		// help out embedded text view which doesn't
668 		// get notified of this
669 		fText->Invalidate();
670 	}
671 }
672 
673 
674 BSize
675 BTextControl::MinSize()
676 {
677 	CALLED();
678 
679 	_ValidateLayoutData();
680 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
681 }
682 
683 
684 BSize
685 BTextControl::MaxSize()
686 {
687 	CALLED();
688 
689 	_ValidateLayoutData();
690 
691 	BSize max = fLayoutData->min;
692 	max.width = B_SIZE_UNLIMITED;
693 
694 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max);
695 }
696 
697 
698 BSize
699 BTextControl::PreferredSize()
700 {
701 	CALLED();
702 
703 	_ValidateLayoutData();
704 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), fLayoutData->min);
705 }
706 
707 
708 void
709 BTextControl::InvalidateLayout(bool descendants)
710 {
711 	CALLED();
712 
713 	fLayoutData->valid = false;
714 
715 	BView::InvalidateLayout(descendants);
716 }
717 
718 
719 BLayoutItem*
720 BTextControl::CreateLabelLayoutItem()
721 {
722 	if (!fLayoutData->label_layout_item)
723 		fLayoutData->label_layout_item = new LabelLayoutItem(this);
724 	return fLayoutData->label_layout_item;
725 }
726 
727 
728 BLayoutItem*
729 BTextControl::CreateTextViewLayoutItem()
730 {
731 	if (!fLayoutData->text_view_layout_item)
732 		fLayoutData->text_view_layout_item = new TextViewLayoutItem(this);
733 	return fLayoutData->text_view_layout_item;
734 }
735 
736 
737 void
738 BTextControl::DoLayout()
739 {
740 	// Bail out, if we shan't do layout.
741 	if (!(Flags() & B_SUPPORTS_LAYOUT))
742 		return;
743 
744 	CALLED();
745 
746 	// If the user set a layout, we let the base class version call its
747 	// hook.
748 	if (GetLayout()) {
749 		BView::DoLayout();
750 		return;
751 	}
752 
753 	_ValidateLayoutData();
754 
755 	// validate current size
756 	BSize size(Bounds().Size());
757 	if (size.width < fLayoutData->min.width)
758 		size.width = fLayoutData->min.width;
759 	if (size.height < fLayoutData->min.height)
760 		size.height = fLayoutData->min.height;
761 
762 	// divider
763 	float divider = 0;
764 	if (fLayoutData->label_layout_item && fLayoutData->text_view_layout_item) {
765 		// We have layout items. They define the divider location.
766 		divider = fLayoutData->text_view_layout_item->Frame().left
767 			- fLayoutData->label_layout_item->Frame().left;
768 	} else {
769 		if (fLayoutData->label_width > 0)
770 			divider = fLayoutData->label_width + 5;
771 	}
772 
773 	// text view
774 	BRect dirty(fText->Frame());
775 	BRect textFrame(divider + 1, kFrameMargin, size.width - 2,
776 		size.height - kFrameMargin);
777 
778 	// place the text view and set the divider
779 	BLayoutUtils::AlignInFrame(fText, textFrame);
780 
781 	fDivider = divider;
782 
783 	// invalidate dirty region
784 	dirty = dirty | fText->Frame();
785 	dirty.InsetBy(-kFrameMargin, -kFrameMargin);
786 
787 	Invalidate(dirty);
788 }
789 
790 
791 // #pragma mark -
792 
793 
794 status_t
795 BTextControl::Perform(perform_code code, void* _data)
796 {
797 	switch (code) {
798 		case PERFORM_CODE_MIN_SIZE:
799 			((perform_data_min_size*)_data)->return_value
800 				= BTextControl::MinSize();
801 			return B_OK;
802 		case PERFORM_CODE_MAX_SIZE:
803 			((perform_data_max_size*)_data)->return_value
804 				= BTextControl::MaxSize();
805 			return B_OK;
806 		case PERFORM_CODE_PREFERRED_SIZE:
807 			((perform_data_preferred_size*)_data)->return_value
808 				= BTextControl::PreferredSize();
809 			return B_OK;
810 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
811 			((perform_data_layout_alignment*)_data)->return_value
812 				= BTextControl::LayoutAlignment();
813 			return B_OK;
814 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
815 			((perform_data_has_height_for_width*)_data)->return_value
816 				= BTextControl::HasHeightForWidth();
817 			return B_OK;
818 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
819 		{
820 			perform_data_get_height_for_width* data
821 				= (perform_data_get_height_for_width*)_data;
822 			BTextControl::GetHeightForWidth(data->width, &data->min, &data->max,
823 				&data->preferred);
824 			return B_OK;
825 }
826 		case PERFORM_CODE_SET_LAYOUT:
827 		{
828 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
829 			BTextControl::SetLayout(data->layout);
830 			return B_OK;
831 		}
832 		case PERFORM_CODE_INVALIDATE_LAYOUT:
833 		{
834 			perform_data_invalidate_layout* data
835 				= (perform_data_invalidate_layout*)_data;
836 			BTextControl::InvalidateLayout(data->descendants);
837 			return B_OK;
838 		}
839 		case PERFORM_CODE_DO_LAYOUT:
840 		{
841 			BTextControl::DoLayout();
842 			return B_OK;
843 		}
844 	}
845 
846 	return BControl::Perform(code, _data);
847 }
848 
849 
850 void BTextControl::_ReservedTextControl1() {}
851 void BTextControl::_ReservedTextControl2() {}
852 void BTextControl::_ReservedTextControl3() {}
853 void BTextControl::_ReservedTextControl4() {}
854 
855 
856 BTextControl &
857 BTextControl::operator=(const BTextControl&)
858 {
859 	return *this;
860 }
861 
862 
863 void
864 BTextControl::_UpdateTextViewColors(bool enabled)
865 {
866 	rgb_color textColor;
867 	rgb_color color;
868 	BFont font;
869 
870 	fText->GetFontAndColor(0, &font);
871 
872 	if (enabled)
873 		textColor = ui_color(B_DOCUMENT_TEXT_COLOR);
874 	else {
875 		textColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
876 			B_DISABLED_LABEL_TINT);
877 	}
878 
879 	fText->SetFontAndColor(&font, B_FONT_ALL, &textColor);
880 
881 	if (enabled) {
882 		color = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
883 	} else {
884 		color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
885 			B_LIGHTEN_2_TINT);
886 	}
887 
888 	fText->SetViewColor(color);
889 	fText->SetLowColor(color);
890 }
891 
892 
893 void
894 BTextControl::_CommitValue()
895 {
896 }
897 
898 
899 void
900 BTextControl::_InitData(const char* label, const char* initialText,
901 	BMessage* archive)
902 {
903 	BRect bounds(Bounds());
904 
905 	fText = NULL;
906 	fModificationMessage = NULL;
907 	fLabelAlign = B_ALIGN_LEFT;
908 	fDivider = 0.0f;
909 	fLayoutData = new LayoutData(bounds.Width(), bounds.Height());
910 
911 	int32 flags = 0;
912 
913 	BFont font(be_plain_font);
914 
915 	if (!archive || !archive->HasString("_fname"))
916 		flags |= B_FONT_FAMILY_AND_STYLE;
917 
918 	if (!archive || !archive->HasFloat("_fflt"))
919 		flags |= B_FONT_SIZE;
920 
921 	if (flags != 0)
922 		SetFont(&font, flags);
923 
924 	if (label)
925 		fDivider = floorf(bounds.Width() / 2.0f);
926 
927 	uint32 navigableFlags = Flags() & B_NAVIGABLE;
928 
929 	if (archive)
930 		fText = static_cast<BPrivate::_BTextInput_*>(FindView("_input_"));
931 
932 	if (fText == NULL) {
933 		BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom);
934 		// we are stroking the frame around the text view, which
935 		// is 2 pixels wide
936 		frame.InsetBy(kFrameMargin, kFrameMargin);
937 		BRect textRect(frame.OffsetToCopy(B_ORIGIN));
938 
939 		fText = new BPrivate::_BTextInput_(frame, textRect,
940 			B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS | navigableFlags);
941 		AddChild(fText);
942 
943 		SetText(initialText);
944 		fText->SetAlignment(B_ALIGN_LEFT);
945 		fText->AlignTextRect();
946 	}
947 }
948 
949 
950 void
951 BTextControl::_ValidateLayout()
952 {
953 	CALLED();
954 
955 	_ValidateLayoutData();
956 
957 	ResizeTo(Bounds().Width(), fLayoutData->min.height);
958 
959 	_LayoutTextView();
960 }
961 
962 
963 void
964 BTextControl::_LayoutTextView()
965 {
966 	CALLED();
967 
968 	BRect frame = Bounds();
969 	frame.left = fDivider;
970 	// we are stroking the frame around the text view, which
971 	// is 2 pixels wide
972 	frame.InsetBy(2.0, 2.0);
973 	fText->MoveTo(frame.left, frame.top);
974 	fText->ResizeTo(frame.Width(), frame.Height());
975 	fText->AlignTextRect();
976 
977 	TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height());
978 	TRACE("fDivider: %.2f\n", fDivider);
979 	TRACE("fText frame: (%.2f, %.2f, %.2f, %.2f)\n",
980 		frame.left, frame.top, frame.right, frame.bottom);
981 }
982 
983 
984 void
985 BTextControl::_UpdateFrame()
986 {
987 	CALLED();
988 
989 	if (fLayoutData->label_layout_item && fLayoutData->text_view_layout_item) {
990 		BRect labelFrame = fLayoutData->label_layout_item->Frame();
991 		BRect textFrame = fLayoutData->text_view_layout_item->Frame();
992 
993 		// update divider
994 		fDivider = textFrame.left - labelFrame.left;
995 
996 		MoveTo(labelFrame.left, labelFrame.top);
997 		BSize oldSize = Bounds().Size();
998 		ResizeTo(textFrame.left + textFrame.Width() - labelFrame.left,
999 			textFrame.top + textFrame.Height() - labelFrame.top);
1000 		BSize newSize = Bounds().Size();
1001 
1002 		// If the size changes, ResizeTo() will trigger a relayout, otherwise
1003 		// we need to do that explicitly.
1004 		if (newSize != oldSize)
1005 			Relayout();
1006 	}
1007 }
1008 
1009 
1010 void
1011 BTextControl::_ValidateLayoutData()
1012 {
1013 	CALLED();
1014 
1015 	if (fLayoutData->valid)
1016 		return;
1017 
1018 	// cache font height
1019 	font_height& fh = fLayoutData->font_info;
1020 	GetFontHeight(&fh);
1021 
1022 	if (Label() != NULL) {
1023 		fLayoutData->label_width = ceilf(StringWidth(Label()));
1024 		fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent);
1025 	} else {
1026 		fLayoutData->label_width = 0;
1027 		fLayoutData->label_height = 0;
1028 	}
1029 
1030 	// compute the minimal divider
1031 	float divider = 0;
1032 	if (fLayoutData->label_width > 0)
1033 		divider = fLayoutData->label_width + 5;
1034 
1035 	// If we shan't do real layout, we let the current divider take influence.
1036 	if (!(Flags() & B_SUPPORTS_LAYOUT))
1037 		divider = max_c(divider, fDivider);
1038 
1039 	// get the minimal (== preferred) text view size
1040 	fLayoutData->text_view_min = fText->MinSize();
1041 
1042 	TRACE("text view min width: %.2f\n", fLayoutData->text_view_min.width);
1043 
1044 	// compute our minimal (== preferred) size
1045 	BSize min(fLayoutData->text_view_min);
1046 	min.width += 2 * kFrameMargin;
1047 	min.height += 2 * kFrameMargin;
1048 
1049 	if (divider > 0)
1050 		min.width += divider;
1051 	if (fLayoutData->label_height > min.height)
1052 		min.height = fLayoutData->label_height;
1053 
1054 	fLayoutData->min = min;
1055 
1056 	fLayoutData->valid = true;
1057 
1058 	TRACE("width: %.2f, height: %.2f\n", min.width, min.height);
1059 }
1060 
1061 
1062 // #pragma mark -
1063 
1064 
1065 BTextControl::LabelLayoutItem::LabelLayoutItem(BTextControl* parent)
1066 	: fParent(parent),
1067 	  fFrame()
1068 {
1069 }
1070 
1071 
1072 bool
1073 BTextControl::LabelLayoutItem::IsVisible()
1074 {
1075 	return !fParent->IsHidden(fParent);
1076 }
1077 
1078 
1079 void
1080 BTextControl::LabelLayoutItem::SetVisible(bool visible)
1081 {
1082 	// not allowed
1083 }
1084 
1085 
1086 BRect
1087 BTextControl::LabelLayoutItem::Frame()
1088 {
1089 	return fFrame;
1090 }
1091 
1092 
1093 void
1094 BTextControl::LabelLayoutItem::SetFrame(BRect frame)
1095 {
1096 	fFrame = frame;
1097 	fParent->_UpdateFrame();
1098 }
1099 
1100 
1101 BView*
1102 BTextControl::LabelLayoutItem::View()
1103 {
1104 	return fParent;
1105 }
1106 
1107 
1108 BSize
1109 BTextControl::LabelLayoutItem::BaseMinSize()
1110 {
1111 	fParent->_ValidateLayoutData();
1112 
1113 	if (!fParent->Label())
1114 		return BSize(-1, -1);
1115 
1116 	return BSize(fParent->fLayoutData->label_width + 5,
1117 		fParent->fLayoutData->label_height);
1118 }
1119 
1120 
1121 BSize
1122 BTextControl::LabelLayoutItem::BaseMaxSize()
1123 {
1124 	return BaseMinSize();
1125 }
1126 
1127 
1128 BSize
1129 BTextControl::LabelLayoutItem::BasePreferredSize()
1130 {
1131 	return BaseMinSize();
1132 }
1133 
1134 
1135 BAlignment
1136 BTextControl::LabelLayoutItem::BaseAlignment()
1137 {
1138 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1139 }
1140 
1141 
1142 // #pragma mark -
1143 
1144 
1145 BTextControl::TextViewLayoutItem::TextViewLayoutItem(BTextControl* parent)
1146 	: fParent(parent),
1147 	  fFrame()
1148 {
1149 	// by default the part right of the divider shall have an unlimited maximum
1150 	// width
1151 	SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
1152 }
1153 
1154 
1155 bool
1156 BTextControl::TextViewLayoutItem::IsVisible()
1157 {
1158 	return !fParent->IsHidden(fParent);
1159 }
1160 
1161 
1162 void
1163 BTextControl::TextViewLayoutItem::SetVisible(bool visible)
1164 {
1165 	// not allowed
1166 }
1167 
1168 
1169 BRect
1170 BTextControl::TextViewLayoutItem::Frame()
1171 {
1172 	return fFrame;
1173 }
1174 
1175 
1176 void
1177 BTextControl::TextViewLayoutItem::SetFrame(BRect frame)
1178 {
1179 	fFrame = frame;
1180 	fParent->_UpdateFrame();
1181 }
1182 
1183 
1184 BView*
1185 BTextControl::TextViewLayoutItem::View()
1186 {
1187 	return fParent;
1188 }
1189 
1190 
1191 BSize
1192 BTextControl::TextViewLayoutItem::BaseMinSize()
1193 {
1194 	fParent->_ValidateLayoutData();
1195 
1196 	BSize size = fParent->fLayoutData->text_view_min;
1197 	size.width += 2 * kFrameMargin;
1198 	size.height += 2 * kFrameMargin;
1199 
1200 	return size;
1201 }
1202 
1203 
1204 BSize
1205 BTextControl::TextViewLayoutItem::BaseMaxSize()
1206 {
1207 	BSize size(BaseMinSize());
1208 	size.width = B_SIZE_UNLIMITED;
1209 	return size;
1210 }
1211 
1212 
1213 BSize
1214 BTextControl::TextViewLayoutItem::BasePreferredSize()
1215 {
1216 	BSize size(BaseMinSize());
1217 	// puh, no idea...
1218 	size.width = 100;
1219 	return size;
1220 }
1221 
1222 
1223 BAlignment
1224 BTextControl::TextViewLayoutItem::BaseAlignment()
1225 {
1226 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1227 }
1228 
1229