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