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