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