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