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