xref: /haiku/src/kits/interface/TextControl.cpp (revision f8da8f3477d3c18142e59d17d05a545982faa5a8)
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 status_t
951 BTextControl::SetIcon(const BBitmap* icon, uint32 flags)
952 {
953 	return BControl::SetIcon(icon, flags);
954 }
955 
956 
957 // #pragma mark -
958 
959 
960 status_t
961 BTextControl::Perform(perform_code code, void* _data)
962 {
963 	switch (code) {
964 		case PERFORM_CODE_MIN_SIZE:
965 			((perform_data_min_size*)_data)->return_value
966 				= BTextControl::MinSize();
967 			return B_OK;
968 		case PERFORM_CODE_MAX_SIZE:
969 			((perform_data_max_size*)_data)->return_value
970 				= BTextControl::MaxSize();
971 			return B_OK;
972 		case PERFORM_CODE_PREFERRED_SIZE:
973 			((perform_data_preferred_size*)_data)->return_value
974 				= BTextControl::PreferredSize();
975 			return B_OK;
976 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
977 			((perform_data_layout_alignment*)_data)->return_value
978 				= BTextControl::LayoutAlignment();
979 			return B_OK;
980 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
981 			((perform_data_has_height_for_width*)_data)->return_value
982 				= BTextControl::HasHeightForWidth();
983 			return B_OK;
984 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
985 		{
986 			perform_data_get_height_for_width* data
987 				= (perform_data_get_height_for_width*)_data;
988 			BTextControl::GetHeightForWidth(data->width, &data->min, &data->max,
989 				&data->preferred);
990 			return B_OK;
991 		}
992 		case PERFORM_CODE_SET_LAYOUT:
993 		{
994 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
995 			BTextControl::SetLayout(data->layout);
996 			return B_OK;
997 		}
998 		case PERFORM_CODE_LAYOUT_INVALIDATED:
999 		{
1000 			perform_data_layout_invalidated* data
1001 				= (perform_data_layout_invalidated*)_data;
1002 			BTextControl::LayoutInvalidated(data->descendants);
1003 			return B_OK;
1004 		}
1005 		case PERFORM_CODE_DO_LAYOUT:
1006 		{
1007 			BTextControl::DoLayout();
1008 			return B_OK;
1009 		}
1010 		case PERFORM_CODE_SET_ICON:
1011 		{
1012 			perform_data_set_icon* data = (perform_data_set_icon*)_data;
1013 			return BTextControl::SetIcon(data->icon, data->flags);
1014 		}
1015 		case PERFORM_CODE_ALL_UNARCHIVED:
1016 		{
1017 			perform_data_all_unarchived* data
1018 				= (perform_data_all_unarchived*)_data;
1019 
1020 			data->return_value = BTextControl::AllUnarchived(data->archive);
1021 			return B_OK;
1022 		}
1023 		case PERFORM_CODE_ALL_ARCHIVED:
1024 		{
1025 			perform_data_all_archived* data
1026 				= (perform_data_all_archived*)_data;
1027 
1028 			data->return_value = BTextControl::AllArchived(data->archive);
1029 			return B_OK;
1030 		}
1031 	}
1032 
1033 	return BControl::Perform(code, _data);
1034 }
1035 
1036 
1037 void BTextControl::_ReservedTextControl1() {}
1038 void BTextControl::_ReservedTextControl2() {}
1039 void BTextControl::_ReservedTextControl3() {}
1040 void BTextControl::_ReservedTextControl4() {}
1041 
1042 
1043 BTextControl &
1044 BTextControl::operator=(const BTextControl&)
1045 {
1046 	return *this;
1047 }
1048 
1049 
1050 void
1051 BTextControl::_UpdateTextViewColors(bool enabled)
1052 {
1053 	rgb_color textColor;
1054 	rgb_color color;
1055 	BFont font;
1056 
1057 	fText->GetFontAndColor(0, &font);
1058 
1059 	if (enabled)
1060 		textColor = ui_color(B_DOCUMENT_TEXT_COLOR);
1061 	else {
1062 		textColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
1063 			B_DISABLED_LABEL_TINT);
1064 	}
1065 
1066 	fText->SetFontAndColor(&font, B_FONT_ALL, &textColor);
1067 
1068 	if (enabled) {
1069 		color = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
1070 	} else {
1071 		color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
1072 			B_LIGHTEN_2_TINT);
1073 	}
1074 
1075 	fText->SetViewColor(color);
1076 	fText->SetLowColor(color);
1077 }
1078 
1079 
1080 void
1081 BTextControl::_CommitValue()
1082 {
1083 }
1084 
1085 
1086 void
1087 BTextControl::_InitData(const char* label, const BMessage* archive)
1088 {
1089 	BRect bounds(Bounds());
1090 
1091 	fText = NULL;
1092 	fModificationMessage = NULL;
1093 	fLabelAlign = B_ALIGN_LEFT;
1094 	fDivider = 0.0f;
1095 	fLayoutData = new LayoutData(bounds.Width(), bounds.Height());
1096 
1097 	int32 flags = 0;
1098 
1099 	BFont font(be_plain_font);
1100 
1101 	if (!archive || !archive->HasString("_fname"))
1102 		flags |= B_FONT_FAMILY_AND_STYLE;
1103 
1104 	if (!archive || !archive->HasFloat("_fflt"))
1105 		flags |= B_FONT_SIZE;
1106 
1107 	if (flags != 0)
1108 		SetFont(&font, flags);
1109 
1110 	if (label)
1111 		fDivider = floorf(bounds.Width() / 2.0f);
1112 }
1113 
1114 
1115 void
1116 BTextControl::_InitText(const char* initialText, const BMessage* archive)
1117 {
1118 	if (archive)
1119 		fText = static_cast<BPrivate::_BTextInput_*>(FindView("_input_"));
1120 
1121 	if (fText == NULL) {
1122 		BRect bounds(Bounds());
1123 		BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom);
1124 		// we are stroking the frame around the text view, which
1125 		// is 2 pixels wide
1126 		frame.InsetBy(kFrameMargin, kFrameMargin);
1127 		BRect textRect(frame.OffsetToCopy(B_ORIGIN));
1128 
1129 		fText = new BPrivate::_BTextInput_(frame, textRect,
1130 			B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS
1131 			| (Flags() & B_NAVIGABLE));
1132 		AddChild(fText);
1133 
1134 		SetText(initialText);
1135 		fText->SetAlignment(B_ALIGN_LEFT);
1136 		fText->AlignTextRect();
1137 	}
1138 
1139 	// Although this is not strictly initializing the text view,
1140 	// it cannot be done while fText is NULL, so it resides here.
1141 	if (archive) {
1142 		int32 labelAlignment = B_ALIGN_LEFT;
1143 		int32 textAlignment = B_ALIGN_LEFT;
1144 
1145 		status_t err = B_OK;
1146 		if (archive->HasInt32("_a_label"))
1147 			err = archive->FindInt32("_a_label", &labelAlignment);
1148 
1149 		if (err == B_OK && archive->HasInt32("_a_text"))
1150 			err = archive->FindInt32("_a_text", &textAlignment);
1151 
1152 		SetAlignment((alignment)labelAlignment, (alignment)textAlignment);
1153 	}
1154 
1155 	uint32 navigableFlags = Flags() & B_NAVIGABLE;
1156 	if (navigableFlags != 0)
1157 		BView::SetFlags(Flags() & ~B_NAVIGABLE);
1158 }
1159 
1160 
1161 void
1162 BTextControl::_ValidateLayout()
1163 {
1164 	CALLED();
1165 
1166 	_ValidateLayoutData();
1167 
1168 	ResizeTo(Bounds().Width(), fLayoutData->min.height);
1169 
1170 	_LayoutTextView();
1171 }
1172 
1173 
1174 void
1175 BTextControl::_LayoutTextView()
1176 {
1177 	CALLED();
1178 
1179 	BRect frame;
1180 	if (fLayoutData->text_view_layout_item != NULL) {
1181 		frame = fLayoutData->text_view_layout_item->FrameInParent();
1182 	} else {
1183 		frame = Bounds();
1184 		frame.left = fDivider;
1185 	}
1186 
1187 	// we are stroking the frame around the text view, which
1188 	// is 2 pixels wide
1189 	frame.InsetBy(kFrameMargin, kFrameMargin);
1190 	fText->MoveTo(frame.left, frame.top);
1191 	fText->ResizeTo(frame.Width(), frame.Height());
1192 	fText->AlignTextRect();
1193 
1194 	TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height());
1195 	TRACE("fDivider: %.2f\n", fDivider);
1196 	TRACE("fText frame: (%.2f, %.2f, %.2f, %.2f)\n",
1197 		frame.left, frame.top, frame.right, frame.bottom);
1198 }
1199 
1200 
1201 void
1202 BTextControl::_UpdateFrame()
1203 {
1204 	CALLED();
1205 
1206 	if (fLayoutData->text_view_layout_item != NULL) {
1207 		BRect textFrame = fLayoutData->text_view_layout_item->Frame();
1208 		BRect labelFrame;
1209 		if (fLayoutData->label_layout_item != NULL)
1210 			labelFrame = fLayoutData->label_layout_item->Frame();
1211 
1212 		BRect frame;
1213 		if (labelFrame.IsValid()) {
1214 			frame = textFrame | labelFrame;
1215 
1216 			// update divider
1217 			fDivider = fabs(textFrame.left - labelFrame.left);
1218 		} else {
1219 			frame = textFrame;
1220 			fDivider = 0;
1221 		}
1222 
1223 		MoveTo(frame.left, frame.top);
1224 		BSize oldSize = Bounds().Size();
1225 		ResizeTo(frame.Width(), frame.Height());
1226 		BSize newSize = Bounds().Size();
1227 
1228 		// If the size changes, ResizeTo() will trigger a relayout, otherwise
1229 		// we need to do that explicitly.
1230 		if (newSize != oldSize)
1231 			Relayout();
1232 	}
1233 }
1234 
1235 
1236 void
1237 BTextControl::_ValidateLayoutData()
1238 {
1239 	CALLED();
1240 
1241 	if (fLayoutData->valid)
1242 		return;
1243 
1244 	// cache font height
1245 	font_height& fh = fLayoutData->font_info;
1246 	GetFontHeight(&fh);
1247 
1248 	if (Label() != NULL) {
1249 		fLayoutData->label_width = ceilf(StringWidth(Label()));
1250 		fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent);
1251 	} else {
1252 		fLayoutData->label_width = 0;
1253 		fLayoutData->label_height = 0;
1254 	}
1255 
1256 	// compute the minimal divider
1257 	float divider = 0;
1258 	if (fLayoutData->label_width > 0) {
1259 		divider = fLayoutData->label_width
1260 			+ be_control_look->DefaultLabelSpacing();
1261 	}
1262 
1263 	// If we shan't do real layout, we let the current divider take influence.
1264 	if (!(Flags() & B_SUPPORTS_LAYOUT))
1265 		divider = max_c(divider, fDivider);
1266 
1267 	// get the minimal (== preferred) text view size
1268 	fLayoutData->text_view_min = fText->MinSize();
1269 
1270 	TRACE("text view min width: %.2f\n", fLayoutData->text_view_min.width);
1271 
1272 	// compute our minimal (== preferred) size
1273 	BSize min(fLayoutData->text_view_min);
1274 	min.width += 2 * kFrameMargin;
1275 	min.height += 2 * kFrameMargin;
1276 
1277 	if (divider > 0)
1278 		min.width += divider;
1279 	if (fLayoutData->label_height > min.height)
1280 		min.height = fLayoutData->label_height;
1281 
1282 	fLayoutData->min = min;
1283 
1284 	fLayoutData->valid = true;
1285 	ResetLayoutInvalidation();
1286 
1287 	TRACE("width: %.2f, height: %.2f\n", min.width, min.height);
1288 }
1289 
1290 
1291 // #pragma mark -
1292 
1293 
1294 BTextControl::LabelLayoutItem::LabelLayoutItem(BTextControl* parent)
1295 	:
1296 	fParent(parent),
1297 	fFrame()
1298 {
1299 }
1300 
1301 
1302 BTextControl::LabelLayoutItem::LabelLayoutItem(BMessage* from)
1303 	:
1304 	BAbstractLayoutItem(from),
1305 	fParent(NULL),
1306 	fFrame()
1307 {
1308 	from->FindRect(kFrameField, &fFrame);
1309 }
1310 
1311 
1312 bool
1313 BTextControl::LabelLayoutItem::IsVisible()
1314 {
1315 	return !fParent->IsHidden(fParent);
1316 }
1317 
1318 
1319 void
1320 BTextControl::LabelLayoutItem::SetVisible(bool visible)
1321 {
1322 	// not allowed
1323 }
1324 
1325 
1326 BRect
1327 BTextControl::LabelLayoutItem::Frame()
1328 {
1329 	return fFrame;
1330 }
1331 
1332 
1333 void
1334 BTextControl::LabelLayoutItem::SetFrame(BRect frame)
1335 {
1336 	fFrame = frame;
1337 	fParent->_UpdateFrame();
1338 }
1339 
1340 
1341 void
1342 BTextControl::LabelLayoutItem::SetParent(BTextControl* parent)
1343 {
1344 	fParent = parent;
1345 }
1346 
1347 
1348 BView*
1349 BTextControl::LabelLayoutItem::View()
1350 {
1351 	return fParent;
1352 }
1353 
1354 
1355 BSize
1356 BTextControl::LabelLayoutItem::BaseMinSize()
1357 {
1358 	fParent->_ValidateLayoutData();
1359 
1360 	if (!fParent->Label())
1361 		return BSize(-1, -1);
1362 
1363 	return BSize(fParent->fLayoutData->label_width
1364 			+ be_control_look->DefaultLabelSpacing(),
1365 		fParent->fLayoutData->label_height);
1366 }
1367 
1368 
1369 BSize
1370 BTextControl::LabelLayoutItem::BaseMaxSize()
1371 {
1372 	return BaseMinSize();
1373 }
1374 
1375 
1376 BSize
1377 BTextControl::LabelLayoutItem::BasePreferredSize()
1378 {
1379 	return BaseMinSize();
1380 }
1381 
1382 
1383 BAlignment
1384 BTextControl::LabelLayoutItem::BaseAlignment()
1385 {
1386 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1387 }
1388 
1389 
1390 BRect
1391 BTextControl::LabelLayoutItem::FrameInParent() const
1392 {
1393 	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
1394 }
1395 
1396 
1397 status_t
1398 BTextControl::LabelLayoutItem::Archive(BMessage* into, bool deep) const
1399 {
1400 	BArchiver archiver(into);
1401 	status_t err = BAbstractLayoutItem::Archive(into, deep);
1402 	if (err == B_OK)
1403 		err = into->AddRect(kFrameField, fFrame);
1404 
1405 	return archiver.Finish(err);
1406 }
1407 
1408 
1409 BArchivable*
1410 BTextControl::LabelLayoutItem::Instantiate(BMessage* from)
1411 {
1412 	if (validate_instantiation(from, "BTextControl::LabelLayoutItem"))
1413 		return new LabelLayoutItem(from);
1414 	return NULL;
1415 }
1416 
1417 
1418 // #pragma mark -
1419 
1420 
1421 BTextControl::TextViewLayoutItem::TextViewLayoutItem(BTextControl* parent)
1422 	:
1423 	fParent(parent),
1424 	fFrame()
1425 {
1426 	// by default the part right of the divider shall have an unlimited maximum
1427 	// width
1428 	SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
1429 }
1430 
1431 
1432 BTextControl::TextViewLayoutItem::TextViewLayoutItem(BMessage* from)
1433 	:
1434 	BAbstractLayoutItem(from),
1435 	fParent(NULL),
1436 	fFrame()
1437 {
1438 	from->FindRect(kFrameField, &fFrame);
1439 }
1440 
1441 
1442 bool
1443 BTextControl::TextViewLayoutItem::IsVisible()
1444 {
1445 	return !fParent->IsHidden(fParent);
1446 }
1447 
1448 
1449 void
1450 BTextControl::TextViewLayoutItem::SetVisible(bool visible)
1451 {
1452 	// not allowed
1453 }
1454 
1455 
1456 BRect
1457 BTextControl::TextViewLayoutItem::Frame()
1458 {
1459 	return fFrame;
1460 }
1461 
1462 
1463 void
1464 BTextControl::TextViewLayoutItem::SetFrame(BRect frame)
1465 {
1466 	fFrame = frame;
1467 	fParent->_UpdateFrame();
1468 }
1469 
1470 
1471 void
1472 BTextControl::TextViewLayoutItem::SetParent(BTextControl* parent)
1473 {
1474 	fParent = parent;
1475 }
1476 
1477 
1478 BView*
1479 BTextControl::TextViewLayoutItem::View()
1480 {
1481 	return fParent;
1482 }
1483 
1484 
1485 BSize
1486 BTextControl::TextViewLayoutItem::BaseMinSize()
1487 {
1488 	fParent->_ValidateLayoutData();
1489 
1490 	BSize size = fParent->fLayoutData->text_view_min;
1491 	size.width += 2 * kFrameMargin;
1492 	size.height += 2 * kFrameMargin;
1493 
1494 	return size;
1495 }
1496 
1497 
1498 BSize
1499 BTextControl::TextViewLayoutItem::BaseMaxSize()
1500 {
1501 	BSize size(BaseMinSize());
1502 	size.width = B_SIZE_UNLIMITED;
1503 	return size;
1504 }
1505 
1506 
1507 BSize
1508 BTextControl::TextViewLayoutItem::BasePreferredSize()
1509 {
1510 	BSize size(BaseMinSize());
1511 	// puh, no idea...
1512 	size.width = 100;
1513 	return size;
1514 }
1515 
1516 
1517 BAlignment
1518 BTextControl::TextViewLayoutItem::BaseAlignment()
1519 {
1520 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1521 }
1522 
1523 
1524 BRect
1525 BTextControl::TextViewLayoutItem::FrameInParent() const
1526 {
1527 	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
1528 }
1529 
1530 
1531 status_t
1532 BTextControl::TextViewLayoutItem::Archive(BMessage* into, bool deep) const
1533 {
1534 	BArchiver archiver(into);
1535 	status_t err = BAbstractLayoutItem::Archive(into, deep);
1536 	if (err == B_OK)
1537 		err = into->AddRect(kFrameField, fFrame);
1538 
1539 	return archiver.Finish(err);
1540 }
1541 
1542 
1543 BArchivable*
1544 BTextControl::TextViewLayoutItem::Instantiate(BMessage* from)
1545 {
1546 	if (validate_instantiation(from, "BTextControl::TextViewLayoutItem"))
1547 		return new TextViewLayoutItem(from);
1548 	return NULL;
1549 }
1550 
1551 
1552 extern "C" void
1553 B_IF_GCC_2(InvalidateLayout__12BTextControlb,
1554 	_ZN12BTextControl16InvalidateLayoutEb)(BView* view, bool descendants)
1555 {
1556 	perform_data_layout_invalidated data;
1557 	data.descendants = descendants;
1558 
1559 	view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);
1560 }
1561 
1562