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