xref: /haiku/src/kits/interface/TextControl.cpp (revision a906d0a031e721e2f2ec9d95274103e74a3a774f)
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 void
845 BTextControl::InvalidateLayout(bool descendants)
846 {
847 	CALLED();
848 
849 	fLayoutData->valid = false;
850 
851 	BView::InvalidateLayout(descendants);
852 }
853 
854 
855 BLayoutItem*
856 BTextControl::CreateLabelLayoutItem()
857 {
858 	if (!fLayoutData->label_layout_item)
859 		fLayoutData->label_layout_item = new LabelLayoutItem(this);
860 	return fLayoutData->label_layout_item;
861 }
862 
863 
864 BLayoutItem*
865 BTextControl::CreateTextViewLayoutItem()
866 {
867 	if (!fLayoutData->text_view_layout_item)
868 		fLayoutData->text_view_layout_item = new TextViewLayoutItem(this);
869 	return fLayoutData->text_view_layout_item;
870 }
871 
872 
873 void
874 BTextControl::DoLayout()
875 {
876 	// Bail out, if we shan't do layout.
877 	if (!(Flags() & B_SUPPORTS_LAYOUT))
878 		return;
879 
880 	CALLED();
881 
882 	// If the user set a layout, we let the base class version call its
883 	// hook.
884 	if (GetLayout()) {
885 		BView::DoLayout();
886 		return;
887 	}
888 
889 	_ValidateLayoutData();
890 
891 	// validate current size
892 	BSize size(Bounds().Size());
893 	if (size.width < fLayoutData->min.width)
894 		size.width = fLayoutData->min.width;
895 	if (size.height < fLayoutData->min.height)
896 		size.height = fLayoutData->min.height;
897 
898 	// divider
899 	float divider = 0;
900 	if (fLayoutData->label_layout_item && fLayoutData->text_view_layout_item) {
901 		// We have layout items. They define the divider location.
902 		divider = fLayoutData->text_view_layout_item->Frame().left
903 			- fLayoutData->label_layout_item->Frame().left;
904 	} else {
905 		if (fLayoutData->label_width > 0)
906 			divider = fLayoutData->label_width + 5;
907 	}
908 
909 	// text view
910 	BRect dirty(fText->Frame());
911 	BRect textFrame(divider + kFrameMargin, kFrameMargin,
912 		size.width - kFrameMargin, size.height - kFrameMargin);
913 
914 	// place the text view and set the divider
915 	BLayoutUtils::AlignInFrame(fText, textFrame);
916 
917 	fDivider = divider;
918 
919 	// invalidate dirty region
920 	dirty = dirty | fText->Frame();
921 	dirty.InsetBy(-kFrameMargin, -kFrameMargin);
922 
923 	Invalidate(dirty);
924 }
925 
926 
927 // #pragma mark -
928 
929 
930 status_t
931 BTextControl::Perform(perform_code code, void* _data)
932 {
933 	switch (code) {
934 		case PERFORM_CODE_MIN_SIZE:
935 			((perform_data_min_size*)_data)->return_value
936 				= BTextControl::MinSize();
937 			return B_OK;
938 		case PERFORM_CODE_MAX_SIZE:
939 			((perform_data_max_size*)_data)->return_value
940 				= BTextControl::MaxSize();
941 			return B_OK;
942 		case PERFORM_CODE_PREFERRED_SIZE:
943 			((perform_data_preferred_size*)_data)->return_value
944 				= BTextControl::PreferredSize();
945 			return B_OK;
946 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
947 			((perform_data_layout_alignment*)_data)->return_value
948 				= BTextControl::LayoutAlignment();
949 			return B_OK;
950 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
951 			((perform_data_has_height_for_width*)_data)->return_value
952 				= BTextControl::HasHeightForWidth();
953 			return B_OK;
954 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
955 		{
956 			perform_data_get_height_for_width* data
957 				= (perform_data_get_height_for_width*)_data;
958 			BTextControl::GetHeightForWidth(data->width, &data->min, &data->max,
959 				&data->preferred);
960 			return B_OK;
961 }
962 		case PERFORM_CODE_SET_LAYOUT:
963 		{
964 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
965 			BTextControl::SetLayout(data->layout);
966 			return B_OK;
967 		}
968 		case PERFORM_CODE_INVALIDATE_LAYOUT:
969 		{
970 			perform_data_invalidate_layout* data
971 				= (perform_data_invalidate_layout*)_data;
972 			BTextControl::InvalidateLayout(data->descendants);
973 			return B_OK;
974 		}
975 		case PERFORM_CODE_DO_LAYOUT:
976 		{
977 			BTextControl::DoLayout();
978 			return B_OK;
979 		}
980 		case PERFORM_CODE_ALL_UNARCHIVED:
981 		{
982 			perform_data_all_unarchived* data
983 				= (perform_data_all_unarchived*)_data;
984 
985 			data->return_value = BTextControl::AllUnarchived(data->archive);
986 			return B_OK;
987 		}
988 		case PERFORM_CODE_ALL_ARCHIVED:
989 		{
990 			perform_data_all_archived* data
991 				= (perform_data_all_archived*)_data;
992 
993 			data->return_value = BTextControl::AllArchived(data->archive);
994 			return B_OK;
995 		}
996 	}
997 
998 	return BControl::Perform(code, _data);
999 }
1000 
1001 
1002 void BTextControl::_ReservedTextControl1() {}
1003 void BTextControl::_ReservedTextControl2() {}
1004 void BTextControl::_ReservedTextControl3() {}
1005 void BTextControl::_ReservedTextControl4() {}
1006 
1007 
1008 BTextControl &
1009 BTextControl::operator=(const BTextControl&)
1010 {
1011 	return *this;
1012 }
1013 
1014 
1015 void
1016 BTextControl::_UpdateTextViewColors(bool enabled)
1017 {
1018 	rgb_color textColor;
1019 	rgb_color color;
1020 	BFont font;
1021 
1022 	fText->GetFontAndColor(0, &font);
1023 
1024 	if (enabled)
1025 		textColor = ui_color(B_DOCUMENT_TEXT_COLOR);
1026 	else {
1027 		textColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
1028 			B_DISABLED_LABEL_TINT);
1029 	}
1030 
1031 	fText->SetFontAndColor(&font, B_FONT_ALL, &textColor);
1032 
1033 	if (enabled) {
1034 		color = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
1035 	} else {
1036 		color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
1037 			B_LIGHTEN_2_TINT);
1038 	}
1039 
1040 	fText->SetViewColor(color);
1041 	fText->SetLowColor(color);
1042 }
1043 
1044 
1045 void
1046 BTextControl::_CommitValue()
1047 {
1048 }
1049 
1050 
1051 void
1052 BTextControl::_InitData(const char* label, const BMessage* archive)
1053 {
1054 	BRect bounds(Bounds());
1055 
1056 	fText = NULL;
1057 	fModificationMessage = NULL;
1058 	fLabelAlign = B_ALIGN_LEFT;
1059 	fDivider = 0.0f;
1060 	fLayoutData = new LayoutData(bounds.Width(), bounds.Height());
1061 
1062 	int32 flags = 0;
1063 
1064 	BFont font(be_plain_font);
1065 
1066 	if (!archive || !archive->HasString("_fname"))
1067 		flags |= B_FONT_FAMILY_AND_STYLE;
1068 
1069 	if (!archive || !archive->HasFloat("_fflt"))
1070 		flags |= B_FONT_SIZE;
1071 
1072 	if (flags != 0)
1073 		SetFont(&font, flags);
1074 
1075 	if (label)
1076 		fDivider = floorf(bounds.Width() / 2.0f);
1077 
1078 }
1079 
1080 
1081 void
1082 BTextControl::_InitText(const char* initialText, const BMessage* archive)
1083 {
1084 	if (archive)
1085 		fText = static_cast<BPrivate::_BTextInput_*>(FindView("_input_"));
1086 
1087 	if (fText == NULL) {
1088 		BRect bounds(Bounds());
1089 		BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom);
1090 		// we are stroking the frame around the text view, which
1091 		// is 2 pixels wide
1092 		frame.InsetBy(kFrameMargin, kFrameMargin);
1093 		BRect textRect(frame.OffsetToCopy(B_ORIGIN));
1094 
1095 		fText = new BPrivate::_BTextInput_(frame, textRect,
1096 			B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS
1097 			| (Flags() & B_NAVIGABLE));
1098 		AddChild(fText);
1099 
1100 		SetText(initialText);
1101 		fText->SetAlignment(B_ALIGN_LEFT);
1102 		fText->AlignTextRect();
1103 	}
1104 
1105 	// Although this is not strictly initializing the text view,
1106 	// it cannot be done while fText is NULL, so it resides here.
1107 	if (archive) {
1108 		int32 labelAlignment = B_ALIGN_LEFT;
1109 		int32 textAlignment = B_ALIGN_LEFT;
1110 
1111 		status_t err = B_OK;
1112 		if (archive->HasInt32("_a_label"))
1113 			err = archive->FindInt32("_a_label", &labelAlignment);
1114 
1115 		if (err == B_OK && archive->HasInt32("_a_text"))
1116 			err = archive->FindInt32("_a_text", &textAlignment);
1117 
1118 		SetAlignment((alignment)labelAlignment, (alignment)textAlignment);
1119 	}
1120 
1121 	uint32 navigableFlags = Flags() & B_NAVIGABLE;
1122 	if (navigableFlags != 0)
1123 		BView::SetFlags(Flags() & ~B_NAVIGABLE);
1124 }
1125 
1126 
1127 void
1128 BTextControl::_ValidateLayout()
1129 {
1130 	CALLED();
1131 
1132 	_ValidateLayoutData();
1133 
1134 	ResizeTo(Bounds().Width(), fLayoutData->min.height);
1135 
1136 	_LayoutTextView();
1137 }
1138 
1139 
1140 void
1141 BTextControl::_LayoutTextView()
1142 {
1143 	CALLED();
1144 
1145 	BRect frame = Bounds();
1146 	frame.left = fDivider;
1147 	// we are stroking the frame around the text view, which
1148 	// is 2 pixels wide
1149 	frame.InsetBy(kFrameMargin, kFrameMargin);
1150 	fText->MoveTo(frame.left, frame.top);
1151 	fText->ResizeTo(frame.Width(), frame.Height());
1152 	fText->AlignTextRect();
1153 
1154 	TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height());
1155 	TRACE("fDivider: %.2f\n", fDivider);
1156 	TRACE("fText frame: (%.2f, %.2f, %.2f, %.2f)\n",
1157 		frame.left, frame.top, frame.right, frame.bottom);
1158 }
1159 
1160 
1161 void
1162 BTextControl::_UpdateFrame()
1163 {
1164 	CALLED();
1165 
1166 	if (fLayoutData->label_layout_item && fLayoutData->text_view_layout_item) {
1167 		BRect labelFrame = fLayoutData->label_layout_item->Frame();
1168 		BRect textFrame = fLayoutData->text_view_layout_item->Frame();
1169 
1170 		// update divider
1171 		fDivider = textFrame.left - labelFrame.left;
1172 
1173 		MoveTo(labelFrame.left, labelFrame.top);
1174 		BSize oldSize = Bounds().Size();
1175 		ResizeTo(textFrame.left + textFrame.Width() - labelFrame.left,
1176 			textFrame.top + textFrame.Height() - labelFrame.top);
1177 		BSize newSize = Bounds().Size();
1178 
1179 		// If the size changes, ResizeTo() will trigger a relayout, otherwise
1180 		// we need to do that explicitly.
1181 		if (newSize != oldSize)
1182 			Relayout();
1183 	}
1184 }
1185 
1186 
1187 void
1188 BTextControl::_ValidateLayoutData()
1189 {
1190 	CALLED();
1191 
1192 	if (fLayoutData->valid)
1193 		return;
1194 
1195 	// cache font height
1196 	font_height& fh = fLayoutData->font_info;
1197 	GetFontHeight(&fh);
1198 
1199 	if (Label() != NULL) {
1200 		fLayoutData->label_width = ceilf(StringWidth(Label()));
1201 		fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent);
1202 	} else {
1203 		fLayoutData->label_width = 0;
1204 		fLayoutData->label_height = 0;
1205 	}
1206 
1207 	// compute the minimal divider
1208 	float divider = 0;
1209 	if (fLayoutData->label_width > 0)
1210 		divider = fLayoutData->label_width + 5;
1211 
1212 	// If we shan't do real layout, we let the current divider take influence.
1213 	if (!(Flags() & B_SUPPORTS_LAYOUT))
1214 		divider = max_c(divider, fDivider);
1215 
1216 	// get the minimal (== preferred) text view size
1217 	fLayoutData->text_view_min = fText->MinSize();
1218 
1219 	TRACE("text view min width: %.2f\n", fLayoutData->text_view_min.width);
1220 
1221 	// compute our minimal (== preferred) size
1222 	BSize min(fLayoutData->text_view_min);
1223 	min.width += 2 * kFrameMargin;
1224 	min.height += 2 * kFrameMargin;
1225 
1226 	if (divider > 0)
1227 		min.width += divider;
1228 	if (fLayoutData->label_height > min.height)
1229 		min.height = fLayoutData->label_height;
1230 
1231 	fLayoutData->min = min;
1232 
1233 	fLayoutData->valid = true;
1234 	ResetLayoutInvalidation();
1235 
1236 	TRACE("width: %.2f, height: %.2f\n", min.width, min.height);
1237 }
1238 
1239 
1240 // #pragma mark -
1241 
1242 
1243 BTextControl::LabelLayoutItem::LabelLayoutItem(BTextControl* parent)
1244 	:
1245 	fParent(parent),
1246 	fFrame()
1247 {
1248 }
1249 
1250 
1251 BTextControl::LabelLayoutItem::LabelLayoutItem(BMessage* from)
1252 	:
1253 	BAbstractLayoutItem(from),
1254 	fParent(NULL),
1255 	fFrame()
1256 {
1257 	from->FindRect(kFrameField, &fFrame);
1258 }
1259 
1260 
1261 bool
1262 BTextControl::LabelLayoutItem::IsVisible()
1263 {
1264 	return !fParent->IsHidden(fParent);
1265 }
1266 
1267 
1268 void
1269 BTextControl::LabelLayoutItem::SetVisible(bool visible)
1270 {
1271 	// not allowed
1272 }
1273 
1274 
1275 BRect
1276 BTextControl::LabelLayoutItem::Frame()
1277 {
1278 	return fFrame;
1279 }
1280 
1281 
1282 void
1283 BTextControl::LabelLayoutItem::SetFrame(BRect frame)
1284 {
1285 	fFrame = frame;
1286 	fParent->_UpdateFrame();
1287 }
1288 
1289 
1290 void
1291 BTextControl::LabelLayoutItem::SetParent(BTextControl* parent)
1292 {
1293 	fParent = parent;
1294 }
1295 
1296 
1297 BView*
1298 BTextControl::LabelLayoutItem::View()
1299 {
1300 	return fParent;
1301 }
1302 
1303 
1304 BSize
1305 BTextControl::LabelLayoutItem::BaseMinSize()
1306 {
1307 	fParent->_ValidateLayoutData();
1308 
1309 	if (!fParent->Label())
1310 		return BSize(-1, -1);
1311 
1312 	return BSize(fParent->fLayoutData->label_width + 5,
1313 		fParent->fLayoutData->label_height);
1314 }
1315 
1316 
1317 BSize
1318 BTextControl::LabelLayoutItem::BaseMaxSize()
1319 {
1320 	return BaseMinSize();
1321 }
1322 
1323 
1324 BSize
1325 BTextControl::LabelLayoutItem::BasePreferredSize()
1326 {
1327 	return BaseMinSize();
1328 }
1329 
1330 
1331 BAlignment
1332 BTextControl::LabelLayoutItem::BaseAlignment()
1333 {
1334 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1335 }
1336 
1337 
1338 status_t
1339 BTextControl::LabelLayoutItem::Archive(BMessage* into, bool deep) const
1340 {
1341 	BArchiver archiver(into);
1342 	status_t err = BAbstractLayoutItem::Archive(into, deep);
1343 	if (err == B_OK)
1344 		err = into->AddRect(kFrameField, fFrame);
1345 
1346 	return archiver.Finish(err);
1347 }
1348 
1349 
1350 BArchivable*
1351 BTextControl::LabelLayoutItem::Instantiate(BMessage* from)
1352 {
1353 	if (validate_instantiation(from, "BTextControl::LabelLayoutItem"))
1354 		return new LabelLayoutItem(from);
1355 	return NULL;
1356 }
1357 
1358 
1359 // #pragma mark -
1360 
1361 
1362 BTextControl::TextViewLayoutItem::TextViewLayoutItem(BTextControl* parent)
1363 	:
1364 	fParent(parent),
1365 	fFrame()
1366 {
1367 	// by default the part right of the divider shall have an unlimited maximum
1368 	// width
1369 	SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
1370 }
1371 
1372 
1373 BTextControl::TextViewLayoutItem::TextViewLayoutItem(BMessage* from)
1374 	:
1375 	BAbstractLayoutItem(from),
1376 	fParent(NULL),
1377 	fFrame()
1378 {
1379 	from->FindRect(kFrameField, &fFrame);
1380 }
1381 
1382 
1383 bool
1384 BTextControl::TextViewLayoutItem::IsVisible()
1385 {
1386 	return !fParent->IsHidden(fParent);
1387 }
1388 
1389 
1390 void
1391 BTextControl::TextViewLayoutItem::SetVisible(bool visible)
1392 {
1393 	// not allowed
1394 }
1395 
1396 
1397 BRect
1398 BTextControl::TextViewLayoutItem::Frame()
1399 {
1400 	return fFrame;
1401 }
1402 
1403 
1404 void
1405 BTextControl::TextViewLayoutItem::SetFrame(BRect frame)
1406 {
1407 	fFrame = frame;
1408 	fParent->_UpdateFrame();
1409 }
1410 
1411 
1412 void
1413 BTextControl::TextViewLayoutItem::SetParent(BTextControl* parent)
1414 {
1415 	fParent = parent;
1416 }
1417 
1418 
1419 BView*
1420 BTextControl::TextViewLayoutItem::View()
1421 {
1422 	return fParent;
1423 }
1424 
1425 
1426 BSize
1427 BTextControl::TextViewLayoutItem::BaseMinSize()
1428 {
1429 	fParent->_ValidateLayoutData();
1430 
1431 	BSize size = fParent->fLayoutData->text_view_min;
1432 	size.width += 2 * kFrameMargin;
1433 	size.height += 2 * kFrameMargin;
1434 
1435 	return size;
1436 }
1437 
1438 
1439 BSize
1440 BTextControl::TextViewLayoutItem::BaseMaxSize()
1441 {
1442 	BSize size(BaseMinSize());
1443 	size.width = B_SIZE_UNLIMITED;
1444 	return size;
1445 }
1446 
1447 
1448 BSize
1449 BTextControl::TextViewLayoutItem::BasePreferredSize()
1450 {
1451 	BSize size(BaseMinSize());
1452 	// puh, no idea...
1453 	size.width = 100;
1454 	return size;
1455 }
1456 
1457 
1458 BAlignment
1459 BTextControl::TextViewLayoutItem::BaseAlignment()
1460 {
1461 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1462 }
1463 
1464 
1465 status_t
1466 BTextControl::TextViewLayoutItem::Archive(BMessage* into, bool deep) const
1467 {
1468 	BArchiver archiver(into);
1469 	status_t err = BAbstractLayoutItem::Archive(into, deep);
1470 	if (err == B_OK)
1471 		err = into->AddRect(kFrameField, fFrame);
1472 
1473 	return archiver.Finish(err);
1474 }
1475 
1476 
1477 BArchivable*
1478 BTextControl::TextViewLayoutItem::Instantiate(BMessage* from)
1479 {
1480 	if (validate_instantiation(from, "BTextControl::TextViewLayoutItem"))
1481 		return new TextViewLayoutItem(from);
1482 	return NULL;
1483 }
1484 
1485 
1486