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