xref: /haiku/src/kits/interface/TextControl.cpp (revision d63ed5844d997a61afd7a6c4b7c9fad0dc01e9f3)
1 /*
2  * Copyright 2001-2015, Haiku Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Frans van Nispen (xlr8@tref.nl)
7  *		Stephan Aßmus <superstippi@gmx.de>
8  *		Ingo Weinhold <bonefish@cs.tu-berlin.de>
9  */
10 
11 
12 /*!	BTextControl displays text that can act like a control. */
13 
14 
15 #include <TextControl.h>
16 
17 #include <string.h>
18 
19 #include <AbstractLayoutItem.h>
20 #include <ControlLook.h>
21 #include <LayoutUtils.h>
22 #include <Message.h>
23 #include <PropertyInfo.h>
24 #include <Region.h>
25 #include <Window.h>
26 
27 #include <binary_compatibility/Interface.h>
28 #include <binary_compatibility/Support.h>
29 
30 #include "TextInput.h"
31 
32 
33 //#define TRACE_TEXT_CONTROL
34 #ifdef TRACE_TEXT_CONTROL
35 #	include <stdio.h>
36 #	include <FunctionTracer.h>
37 	static int32 sFunctionDepth = -1;
38 #	define CALLED(x...)	FunctionTracer _ft("BTextControl", __FUNCTION__, \
39 							sFunctionDepth)
40 #	define TRACE(x...)	{ BString _to; \
41 							_to.Append(' ', (sFunctionDepth + 1) * 2); \
42 							printf("%s", _to.String()); printf(x); }
43 #else
44 #	define CALLED(x...)
45 #	define TRACE(x...)
46 #endif
47 
48 
49 namespace {
50 	const char* const kFrameField = "BTextControl:layoutitem:frame";
51 	const char* const kTextViewItemField = "BTextControl:textViewItem";
52 	const char* const kLabelItemField = "BMenuField:labelItem";
53 }
54 
55 
56 static property_info sPropertyList[] = {
57 	{
58 		"Value",
59 		{ B_GET_PROPERTY, B_SET_PROPERTY },
60 		{ B_DIRECT_SPECIFIER },
61 		NULL, 0,
62 		{ B_STRING_TYPE }
63 	},
64 	{}
65 };
66 
67 
68 class BTextControl::LabelLayoutItem : public BAbstractLayoutItem {
69 public:
70 								LabelLayoutItem(BTextControl* parent);
71 								LabelLayoutItem(BMessage* from);
72 
73 	virtual	bool				IsVisible();
74 	virtual	void				SetVisible(bool visible);
75 
76 	virtual	BRect				Frame();
77 	virtual	void				SetFrame(BRect frame);
78 
79 			void				SetParent(BTextControl* parent);
80 	virtual	BView*				View();
81 
82 	virtual	BSize				BaseMinSize();
83 	virtual	BSize				BaseMaxSize();
84 	virtual	BSize				BasePreferredSize();
85 	virtual	BAlignment			BaseAlignment();
86 
87 			BRect				FrameInParent() const;
88 
89 	virtual status_t			Archive(BMessage* into, bool deep = true) const;
90 	static	BArchivable*		Instantiate(BMessage* from);
91 
92 private:
93 			BTextControl*		fParent;
94 			BRect				fFrame;
95 };
96 
97 
98 class BTextControl::TextViewLayoutItem : public BAbstractLayoutItem {
99 public:
100 								TextViewLayoutItem(BTextControl* parent);
101 								TextViewLayoutItem(BMessage* from);
102 
103 	virtual	bool				IsVisible();
104 	virtual	void				SetVisible(bool visible);
105 
106 	virtual	BRect				Frame();
107 	virtual	void				SetFrame(BRect frame);
108 
109 			void				SetParent(BTextControl* parent);
110 	virtual	BView*				View();
111 
112 	virtual	BSize				BaseMinSize();
113 	virtual	BSize				BaseMaxSize();
114 	virtual	BSize				BasePreferredSize();
115 	virtual	BAlignment			BaseAlignment();
116 
117 			BRect				FrameInParent() const;
118 
119 	virtual status_t			Archive(BMessage* into, bool deep = true) const;
120 	static	BArchivable*		Instantiate(BMessage* from);
121 private:
122 			BTextControl*		fParent;
123 			BRect				fFrame;
124 };
125 
126 
127 struct BTextControl::LayoutData {
128 	LayoutData(float width, float height)
129 		:
130 		label_layout_item(NULL),
131 		text_view_layout_item(NULL),
132 		previous_width(width),
133 		previous_height(height),
134 		valid(false)
135 	{
136 	}
137 
138 	LabelLayoutItem*	label_layout_item;
139 	TextViewLayoutItem*	text_view_layout_item;
140 	float				previous_width;		// used in FrameResized() for
141 	float				previous_height;	// invalidation
142 	font_height			font_info;
143 	float				label_width;
144 	float				label_height;
145 	BSize				min;
146 	BSize				text_view_min;
147 	bool				valid;
148 };
149 
150 
151 static const int32 kFrameMargin = 2;
152 static const int32 kLabelInputSpacing = 3;
153 
154 
155 // #pragma mark - BTextControl
156 
157 
158 BTextControl::BTextControl(BRect frame, const char* name, const char* label,
159 	const char* text, BMessage* message, uint32 resizeMask, uint32 flags)
160 	:
161 	BControl(frame, name, label, message, resizeMask, flags | B_FRAME_EVENTS)
162 {
163 	_InitData(label);
164 	_InitText(text);
165 	_ValidateLayout();
166 }
167 
168 
169 BTextControl::BTextControl(const char* name, const char* label,
170 	const char* text, BMessage* message, uint32 flags)
171 	:
172 	BControl(name, label, message, flags | B_FRAME_EVENTS)
173 {
174 	_InitData(label);
175 	_InitText(text);
176 	_ValidateLayout();
177 }
178 
179 
180 BTextControl::BTextControl(const char* label, const char* text,
181 	BMessage* message)
182 	:
183 	BControl(NULL, label, message,
184 		B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS)
185 {
186 	_InitData(label);
187 	_InitText(text);
188 	_ValidateLayout();
189 }
190 
191 
192 BTextControl::~BTextControl()
193 {
194 	SetModificationMessage(NULL);
195 	delete fLayoutData;
196 }
197 
198 
199 //	#pragma mark - Archiving
200 
201 
202 BTextControl::BTextControl(BMessage* archive)
203 	:
204 	BControl(BUnarchiver::PrepareArchive(archive))
205 {
206 	BUnarchiver unarchiver(archive);
207 
208 	_InitData(Label(), archive);
209 
210 	if (!BUnarchiver::IsArchiveManaged(archive))
211 		_InitText(NULL, archive);
212 
213 	status_t err = B_OK;
214 	if (archive->HasFloat("_divide"))
215 		err = archive->FindFloat("_divide", &fDivider);
216 
217 	if (err == B_OK && archive->HasMessage("_mod_msg")) {
218 		BMessage* message = new BMessage;
219 		err = archive->FindMessage("_mod_msg", message);
220 		SetModificationMessage(message);
221 	}
222 
223 	unarchiver.Finish(err);
224 }
225 
226 
227 BArchivable*
228 BTextControl::Instantiate(BMessage* archive)
229 {
230 	if (validate_instantiation(archive, "BTextControl"))
231 		return new BTextControl(archive);
232 
233 	return NULL;
234 }
235 
236 
237 status_t
238 BTextControl::Archive(BMessage* data, bool deep) const
239 {
240 	BArchiver archiver(data);
241 	status_t result = BControl::Archive(data, deep);
242 
243 	alignment labelAlignment;
244 	alignment textAlignment;
245 	if (result == B_OK)
246 		GetAlignment(&labelAlignment, &textAlignment);
247 
248 	if (result == B_OK)
249 		result = data->AddInt32("_a_label", labelAlignment);
250 
251 	if (result == B_OK)
252 		result = data->AddInt32("_a_text", textAlignment);
253 
254 	if (result == B_OK)
255 		result = data->AddFloat("_divide", Divider());
256 
257 	if (result == B_OK && ModificationMessage() != NULL)
258 		result = data->AddMessage("_mod_msg", ModificationMessage());
259 
260 	return archiver.Finish(result);
261 }
262 
263 
264 status_t
265 BTextControl::AllArchived(BMessage* into) const
266 {
267 	BArchiver archiver(into);
268 	status_t err = B_OK;
269 
270 	if (archiver.IsArchived(fLayoutData->text_view_layout_item)) {
271 		err = archiver.AddArchivable(kTextViewItemField,
272 			fLayoutData->text_view_layout_item);
273 	}
274 
275 	if (err == B_OK && archiver.IsArchived(fLayoutData->label_layout_item)) {
276 		err = archiver.AddArchivable(kLabelItemField,
277 			fLayoutData->label_layout_item);
278 	}
279 
280 	return err;
281 }
282 
283 
284 status_t
285 BTextControl::AllUnarchived(const BMessage* from)
286 {
287 	status_t err;
288 	if ((err = BControl::AllUnarchived(from)) != B_OK)
289 		return err;
290 
291 	_InitText(NULL, from);
292 
293 	BUnarchiver unarchiver(from);
294 	if (unarchiver.IsInstantiated(kTextViewItemField)) {
295 		err = unarchiver.FindObject(kTextViewItemField,
296 			BUnarchiver::B_DONT_ASSUME_OWNERSHIP,
297 			fLayoutData->text_view_layout_item);
298 
299 		if (err == B_OK)
300 			fLayoutData->text_view_layout_item->SetParent(this);
301 		else
302 			return err;
303 	}
304 
305 	if (unarchiver.IsInstantiated(kLabelItemField)) {
306 		err = unarchiver.FindObject(kLabelItemField,
307 			BUnarchiver::B_DONT_ASSUME_OWNERSHIP,
308 			fLayoutData->label_layout_item);
309 
310 		if (err == B_OK)
311 			fLayoutData->label_layout_item->SetParent(this);
312 	}
313 	return err;
314 }
315 
316 
317 //	#pragma mark - Hook methods
318 
319 
320 void
321 BTextControl::AllAttached()
322 {
323 	BControl::AllAttached();
324 }
325 
326 
327 void
328 BTextControl::AllDetached()
329 {
330 	BControl::AllDetached();
331 }
332 
333 
334 void
335 BTextControl::AttachedToWindow()
336 {
337 	BControl::AttachedToWindow();
338 
339 	_UpdateTextViewColors(IsEnabled());
340 	fText->MakeEditable(IsEnabled());
341 }
342 
343 
344 void
345 BTextControl::DetachedFromWindow()
346 {
347 	BControl::DetachedFromWindow();
348 }
349 
350 
351 void
352 BTextControl::Draw(BRect updateRect)
353 {
354 	bool enabled = IsEnabled();
355 	bool active = fText->IsFocus() && Window()->IsActive();
356 
357 	BRect rect = fText->Frame();
358 	rect.InsetBy(-2, -2);
359 
360 	rgb_color base = ViewColor();
361 	uint32 flags = fLook;
362 	if (!enabled)
363 		flags |= BControlLook::B_DISABLED;
364 
365 	if (active)
366 		flags |= BControlLook::B_FOCUSED;
367 
368 	be_control_look->DrawTextControlBorder(this, rect, updateRect, base,
369 		flags);
370 
371 	if (Label() != NULL) {
372 		if (fLayoutData->label_layout_item != NULL) {
373 			rect = fLayoutData->label_layout_item->FrameInParent();
374 		} else {
375 			rect = Bounds();
376 			rect.right = fDivider - kLabelInputSpacing;
377 		}
378 
379 		// erase the is control flag before drawing the label so that the label
380 		// will get drawn using B_PANEL_TEXT_COLOR
381 		flags &= ~BControlLook::B_IS_CONTROL;
382 
383 		be_control_look->DrawLabel(this, Label(), rect, updateRect,
384 			base, flags, BAlignment(fLabelAlign, B_ALIGN_MIDDLE));
385 	}
386 }
387 
388 
389 void
390 BTextControl::FrameMoved(BPoint newPosition)
391 {
392 	BControl::FrameMoved(newPosition);
393 }
394 
395 
396 void
397 BTextControl::FrameResized(float width, float height)
398 {
399 	CALLED();
400 
401 	BControl::FrameResized(width, height);
402 
403 	// TODO: this causes flickering still...
404 
405 	// changes in width
406 
407 	BRect bounds = Bounds();
408 
409 	if (bounds.Width() > fLayoutData->previous_width) {
410 		// invalidate the region between the old and the new right border
411 		BRect rect = bounds;
412 		rect.left += fLayoutData->previous_width - kFrameMargin;
413 		rect.right--;
414 		Invalidate(rect);
415 	} else if (bounds.Width() < fLayoutData->previous_width) {
416 		// invalidate the region of the new right border
417 		BRect rect = bounds;
418 		rect.left = rect.right - kFrameMargin;
419 		Invalidate(rect);
420 	}
421 
422 	// changes in height
423 
424 	if (bounds.Height() > fLayoutData->previous_height) {
425 		// invalidate the region between the old and the new bottom border
426 		BRect rect = bounds;
427 		rect.top += fLayoutData->previous_height - kFrameMargin;
428 		rect.bottom--;
429 		Invalidate(rect);
430 		// invalidate label area
431 		rect = bounds;
432 		rect.right = fDivider;
433 		Invalidate(rect);
434 	} else if (bounds.Height() < fLayoutData->previous_height) {
435 		// invalidate the region of the new bottom border
436 		BRect rect = bounds;
437 		rect.top = rect.bottom - kFrameMargin;
438 		Invalidate(rect);
439 		// invalidate label area
440 		rect = bounds;
441 		rect.right = fDivider;
442 		Invalidate(rect);
443 	}
444 
445 	fLayoutData->previous_width = bounds.Width();
446 	fLayoutData->previous_height = bounds.Height();
447 
448 	TRACE("width: %.2f, height: %.2f\n", bounds.Width(), bounds.Height());
449 }
450 
451 
452 status_t
453 BTextControl::Invoke(BMessage* message)
454 {
455 	return BControl::Invoke(message);
456 }
457 
458 
459 void
460 BTextControl::LayoutInvalidated(bool descendants)
461 {
462 	CALLED();
463 
464 	fLayoutData->valid = false;
465 }
466 
467 
468 void
469 BTextControl::MessageReceived(BMessage* message)
470 {
471 	if (message->what == B_COLORS_UPDATED) {
472 
473 		if (message->HasColor(ui_color_name(B_PANEL_BACKGROUND_COLOR))
474 			|| message->HasColor(ui_color_name(B_PANEL_TEXT_COLOR))
475 			|| message->HasColor(ui_color_name(B_DOCUMENT_BACKGROUND_COLOR))
476 			|| message->HasColor(ui_color_name(B_DOCUMENT_TEXT_COLOR))) {
477 			_UpdateTextViewColors(IsEnabled());
478 		}
479 	}
480 
481 	if (message->what == B_GET_PROPERTY || message->what == B_SET_PROPERTY) {
482 		BMessage reply(B_REPLY);
483 		bool handled = false;
484 
485 		BMessage specifier;
486 		int32 index;
487 		int32 form;
488 		const char* property;
489 		if (message->GetCurrentSpecifier(&index, &specifier, &form, &property) == B_OK) {
490 			if (strcmp(property, "Value") == 0) {
491 				if (message->what == B_GET_PROPERTY) {
492 					reply.AddString("result", fText->Text());
493 					handled = true;
494 				} else {
495 					const char* value = NULL;
496 					// B_SET_PROPERTY
497 					if (message->FindString("data", &value) == B_OK) {
498 						fText->SetText(value);
499 						reply.AddInt32("error", B_OK);
500 						handled = true;
501 					}
502 				}
503 			}
504 		}
505 
506 		if (handled) {
507 			message->SendReply(&reply);
508 			return;
509 		}
510 	}
511 
512 	BControl::MessageReceived(message);
513 }
514 
515 
516 void
517 BTextControl::MouseDown(BPoint where)
518 {
519 	if (!fText->IsFocus())
520 		fText->MakeFocus(true);
521 }
522 
523 
524 void
525 BTextControl::MouseMoved(BPoint where, uint32 transit,
526 	const BMessage* dragMessage)
527 {
528 	BControl::MouseMoved(where, transit, dragMessage);
529 }
530 
531 
532 void
533 BTextControl::MouseUp(BPoint where)
534 {
535 	BControl::MouseUp(where);
536 }
537 
538 
539 void
540 BTextControl::WindowActivated(bool active)
541 {
542 	if (fText->IsFocus()) {
543 		// invalidate to remove/show focus indication
544 		BRect rect = fText->Frame();
545 		rect.InsetBy(-1, -1);
546 		Invalidate(rect);
547 
548 		// help out embedded text view which doesn't
549 		// get notified of this
550 		fText->Invalidate();
551 	}
552 }
553 
554 
555 //	#pragma mark - Getters and Setters
556 
557 
558 void
559 BTextControl::SetText(const char* text)
560 {
561 	if (InvokeKind() != B_CONTROL_INVOKED)
562 		return;
563 
564 	CALLED();
565 
566 	fText->SetText(text);
567 
568 	if (fText->IsFocus()) {
569 		fText->SetInitialText();
570 		fText->SelectAll();
571 	}
572 
573 	fText->Invalidate();
574 }
575 
576 
577 const char*
578 BTextControl::Text() const
579 {
580 	return fText->Text();
581 }
582 
583 
584 int32
585 BTextControl::TextLength() const
586 {
587 	return fText->TextLength();
588 }
589 
590 
591 void
592 BTextControl::MarkAsInvalid(bool invalid)
593 {
594 	uint32 look = fLook;
595 
596 	if (invalid)
597 		fLook |= BControlLook::B_INVALID;
598 	else
599 		fLook &= ~BControlLook::B_INVALID;
600 
601 	if (look != fLook)
602 		Invalidate();
603 }
604 
605 
606 void
607 BTextControl::SetValue(int32 value)
608 {
609 	BControl::SetValue(value);
610 }
611 
612 
613 BTextView*
614 BTextControl::TextView() const
615 {
616 	return fText;
617 }
618 
619 
620 void
621 BTextControl::SetModificationMessage(BMessage* message)
622 {
623 	delete fModificationMessage;
624 	fModificationMessage = message;
625 }
626 
627 
628 BMessage*
629 BTextControl::ModificationMessage() const
630 {
631 	return fModificationMessage;
632 }
633 
634 
635 void
636 BTextControl::SetAlignment(alignment labelAlignment, alignment textAlignment)
637 {
638 	fText->SetAlignment(textAlignment);
639 	fText->AlignTextRect();
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 
914 	fDivider = divider;
915 
916 	// invalidate dirty region
917 	dirty = dirty | fText->Frame();
918 	dirty.InsetBy(-kFrameMargin, -kFrameMargin);
919 
920 	Invalidate(dirty);
921 }
922 
923 
924 // #pragma mark - protected methods
925 
926 
927 status_t
928 BTextControl::SetIcon(const BBitmap* icon, uint32 flags)
929 {
930 	return BControl::SetIcon(icon, flags);
931 }
932 
933 
934 // #pragma mark - private methods
935 
936 
937 status_t
938 BTextControl::Perform(perform_code code, void* _data)
939 {
940 	switch (code) {
941 		case PERFORM_CODE_MIN_SIZE:
942 			((perform_data_min_size*)_data)->return_value
943 				= BTextControl::MinSize();
944 			return B_OK;
945 
946 		case PERFORM_CODE_MAX_SIZE:
947 			((perform_data_max_size*)_data)->return_value
948 				= BTextControl::MaxSize();
949 			return B_OK;
950 
951 		case PERFORM_CODE_PREFERRED_SIZE:
952 			((perform_data_preferred_size*)_data)->return_value
953 				= BTextControl::PreferredSize();
954 			return B_OK;
955 
956 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
957 			((perform_data_layout_alignment*)_data)->return_value
958 				= BTextControl::LayoutAlignment();
959 			return B_OK;
960 
961 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
962 			((perform_data_has_height_for_width*)_data)->return_value
963 				= BTextControl::HasHeightForWidth();
964 			return B_OK;
965 
966 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
967 		{
968 			perform_data_get_height_for_width* data
969 				= (perform_data_get_height_for_width*)_data;
970 			BTextControl::GetHeightForWidth(data->width, &data->min, &data->max,
971 				&data->preferred);
972 			return B_OK;
973 		}
974 
975 		case PERFORM_CODE_SET_LAYOUT:
976 		{
977 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
978 			BTextControl::SetLayout(data->layout);
979 			return B_OK;
980 		}
981 
982 		case PERFORM_CODE_LAYOUT_INVALIDATED:
983 		{
984 			perform_data_layout_invalidated* data
985 				= (perform_data_layout_invalidated*)_data;
986 			BTextControl::LayoutInvalidated(data->descendants);
987 			return B_OK;
988 		}
989 
990 		case PERFORM_CODE_DO_LAYOUT:
991 		{
992 			BTextControl::DoLayout();
993 			return B_OK;
994 		}
995 
996 		case PERFORM_CODE_SET_ICON:
997 		{
998 			perform_data_set_icon* data = (perform_data_set_icon*)_data;
999 			return BTextControl::SetIcon(data->icon, data->flags);
1000 		}
1001 
1002 		case PERFORM_CODE_ALL_UNARCHIVED:
1003 		{
1004 			perform_data_all_unarchived* data
1005 				= (perform_data_all_unarchived*)_data;
1006 			data->return_value = BTextControl::AllUnarchived(data->archive);
1007 			return B_OK;
1008 		}
1009 
1010 		case PERFORM_CODE_ALL_ARCHIVED:
1011 		{
1012 			perform_data_all_archived* data
1013 				= (perform_data_all_archived*)_data;
1014 			data->return_value = BTextControl::AllArchived(data->archive);
1015 			return B_OK;
1016 		}
1017 	}
1018 
1019 	return BControl::Perform(code, _data);
1020 }
1021 
1022 
1023 //	#pragma mark - FBC padding
1024 
1025 
1026 void BTextControl::_ReservedTextControl1() {}
1027 void BTextControl::_ReservedTextControl2() {}
1028 void BTextControl::_ReservedTextControl3() {}
1029 void BTextControl::_ReservedTextControl4() {}
1030 
1031 
1032 BTextControl&
1033 BTextControl::operator=(const BTextControl&)
1034 {
1035 	return *this;
1036 }
1037 
1038 
1039 void
1040 BTextControl::_UpdateTextViewColors(bool enable)
1041 {
1042 	rgb_color textColor = ui_color(B_DOCUMENT_TEXT_COLOR);
1043 	rgb_color viewColor = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
1044 	BFont font;
1045 
1046 	fText->GetFontAndColor(0, &font);
1047 
1048 	if (!enable) {
1049 		textColor = disable_color(textColor, ViewColor());
1050 		viewColor = disable_color(ViewColor(), viewColor);
1051 	}
1052 
1053 	fText->SetFontAndColor(&font, B_FONT_ALL, &textColor);
1054 	fText->SetViewColor(viewColor);
1055 	fText->SetLowColor(viewColor);
1056 }
1057 
1058 
1059 void
1060 BTextControl::_CommitValue()
1061 {
1062 }
1063 
1064 
1065 void
1066 BTextControl::_InitData(const char* label, const BMessage* archive)
1067 {
1068 	BRect bounds(Bounds());
1069 
1070 	fText = NULL;
1071 	fModificationMessage = NULL;
1072 	fLabelAlign = B_ALIGN_LEFT;
1073 	fDivider = 0.0f;
1074 	fLayoutData = new LayoutData(bounds.Width(), bounds.Height());
1075 
1076 	int32 flags = 0;
1077 
1078 	BFont font(be_plain_font);
1079 
1080 	if (!archive || !archive->HasString("_fname"))
1081 		flags |= B_FONT_FAMILY_AND_STYLE;
1082 
1083 	if (!archive || !archive->HasFloat("_fflt"))
1084 		flags |= B_FONT_SIZE;
1085 
1086 	if (flags != 0)
1087 		SetFont(&font, flags);
1088 
1089 	if (label != NULL)
1090 		fDivider = floorf(bounds.Width() / 2.0f);
1091 
1092 	fLook = 0;
1093 }
1094 
1095 
1096 void
1097 BTextControl::_InitText(const char* initialText, const BMessage* archive)
1098 {
1099 	if (archive)
1100 		fText = static_cast<BPrivate::_BTextInput_*>(FindView("_input_"));
1101 
1102 	if (fText == NULL) {
1103 		BRect bounds(Bounds());
1104 		BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom);
1105 		// we are stroking the frame around the text view, which
1106 		// is 2 pixels wide
1107 		frame.InsetBy(kFrameMargin, kFrameMargin);
1108 		BRect textRect(frame.OffsetToCopy(B_ORIGIN));
1109 
1110 		fText = new BPrivate::_BTextInput_(frame, textRect,
1111 			B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS
1112 			| (Flags() & B_NAVIGABLE));
1113 		AddChild(fText);
1114 
1115 		SetText(initialText);
1116 		fText->SetAlignment(B_ALIGN_LEFT);
1117 		fText->AlignTextRect();
1118 	}
1119 
1120 	// Although this is not strictly initializing the text view,
1121 	// it cannot be done while fText is NULL, so it resides here.
1122 	if (archive) {
1123 		int32 labelAlignment = B_ALIGN_LEFT;
1124 		int32 textAlignment = B_ALIGN_LEFT;
1125 
1126 		status_t err = B_OK;
1127 		if (archive->HasInt32("_a_label"))
1128 			err = archive->FindInt32("_a_label", &labelAlignment);
1129 
1130 		if (err == B_OK && archive->HasInt32("_a_text"))
1131 			err = archive->FindInt32("_a_text", &textAlignment);
1132 
1133 		SetAlignment((alignment)labelAlignment, (alignment)textAlignment);
1134 	}
1135 
1136 	uint32 navigableFlags = Flags() & B_NAVIGABLE;
1137 	if (navigableFlags != 0)
1138 		BView::SetFlags(Flags() & ~B_NAVIGABLE);
1139 }
1140 
1141 
1142 void
1143 BTextControl::_ValidateLayout()
1144 {
1145 	CALLED();
1146 
1147 	_ValidateLayoutData();
1148 
1149 	ResizeTo(Bounds().Width(), fLayoutData->min.height);
1150 
1151 	_LayoutTextView();
1152 }
1153 
1154 
1155 void
1156 BTextControl::_LayoutTextView()
1157 {
1158 	CALLED();
1159 
1160 	BRect frame;
1161 	if (fLayoutData->text_view_layout_item != NULL) {
1162 		frame = fLayoutData->text_view_layout_item->FrameInParent();
1163 	} else {
1164 		frame = Bounds();
1165 		frame.left = fDivider;
1166 	}
1167 
1168 	// we are stroking the frame around the text view, which
1169 	// is 2 pixels wide
1170 	frame.InsetBy(kFrameMargin, kFrameMargin);
1171 	fText->MoveTo(frame.left, frame.top);
1172 	fText->ResizeTo(frame.Width(), frame.Height());
1173 	fText->AlignTextRect();
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