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