xref: /haiku/src/kits/interface/TextControl.cpp (revision 6e434fd80e4640c64031faf5e49720c5672fc470)
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 darken1 = tint_color(noTint, B_DARKEN_1_TINT);
444 	rgb_color darken2 = tint_color(noTint, B_DARKEN_2_TINT);
445 	rgb_color darken4 = tint_color(noTint, B_DARKEN_4_TINT);
446 	rgb_color navigationColor = ui_color(B_KEYBOARD_NAVIGATION_COLOR);
447 
448 	if (enabled)
449 		SetHighColor(darken1);
450 	else
451 		SetHighColor(noTint);
452 
453 	StrokeLine(rect.LeftBottom(), rect.LeftTop());
454 	StrokeLine(rect.RightTop());
455 
456 	if (enabled)
457 		SetHighColor(lighten2);
458 	else
459 		SetHighColor(lighten1);
460 
461 	StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom());
462 	StrokeLine(BPoint(rect.right, rect.top + 1.0f), rect.RightBottom());
463 
464 	// inner bevel
465 
466 	rect.InsetBy(1.0f, 1.0f);
467 
468 	if (active) {
469 		SetHighColor(navigationColor);
470 		StrokeRect(rect);
471 	} else {
472 		if (enabled)
473 			SetHighColor(darken4);
474 		else
475 			SetHighColor(darken2);
476 
477 		StrokeLine(rect.LeftTop(), rect.LeftBottom());
478 		StrokeLine(rect.LeftTop(), rect.RightTop());
479 
480 		SetHighColor(noTint);
481 		StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom());
482 		StrokeLine(BPoint(rect.right, rect.top + 1.0f));
483 	}
484 
485 	// label
486 
487 	if (Label()) {
488 		_ValidateLayoutData();
489 		font_height& fontHeight = fLayoutData->font_info;
490 
491 		float y = Bounds().top + (Bounds().Height() + 1 - fontHeight.ascent
492 			- fontHeight.descent) / 2 + fontHeight.ascent;
493 		float x;
494 
495 		float labelWidth = StringWidth(Label());
496 		switch (fLabelAlign) {
497 			case B_ALIGN_RIGHT:
498 				x = fDivider - labelWidth - kLabelInputSpacing;
499 				break;
500 
501 			case B_ALIGN_CENTER:
502 				x = fDivider - labelWidth / 2.0;
503 				break;
504 
505 			default:
506 				x = 0.0;
507 				break;
508 		}
509 
510 		BRect labelArea(x, Bounds().top, x + labelWidth, Bounds().bottom);
511 		if (x < fDivider && updateRect.Intersects(labelArea)) {
512 			labelArea.right = fText->Frame().left - kLabelInputSpacing;
513 
514 			BRegion clipRegion(labelArea);
515 			ConstrainClippingRegion(&clipRegion);
516 			SetHighColor(IsEnabled() ? ui_color(B_CONTROL_TEXT_COLOR)
517 				: tint_color(noTint, B_DISABLED_LABEL_TINT));
518 			DrawString(Label(), BPoint(x, y));
519 		}
520 	}
521 }
522 
523 
524 void
525 BTextControl::MouseDown(BPoint where)
526 {
527 	if (!fText->IsFocus()) {
528 		fText->MakeFocus(true);
529 	}
530 }
531 
532 
533 void
534 BTextControl::AttachedToWindow()
535 {
536 	BControl::AttachedToWindow();
537 
538 	_UpdateTextViewColors(IsEnabled());
539 	fText->MakeEditable(IsEnabled());
540 }
541 
542 
543 void
544 BTextControl::MakeFocus(bool state)
545 {
546 	if (state != fText->IsFocus()) {
547 		fText->MakeFocus(state);
548 
549 		if (state)
550 			fText->SelectAll();
551 	}
552 }
553 
554 
555 void
556 BTextControl::SetEnabled(bool enabled)
557 {
558 	if (IsEnabled() == enabled)
559 		return;
560 
561 	if (Window()) {
562 		fText->MakeEditable(enabled);
563 		if (enabled)
564 			fText->SetFlags(fText->Flags() | B_NAVIGABLE);
565 		else
566 			fText->SetFlags(fText->Flags() & ~B_NAVIGABLE);
567 
568 		_UpdateTextViewColors(enabled);
569 
570 		fText->Invalidate();
571 		Window()->UpdateIfNeeded();
572 	}
573 
574 	BControl::SetEnabled(enabled);
575 }
576 
577 
578 void
579 BTextControl::GetPreferredSize(float *_width, float *_height)
580 {
581 	CALLED();
582 
583 	_ValidateLayoutData();
584 
585 	if (_width) {
586 		float minWidth = fLayoutData->min.width;
587 		if (Label() == NULL && !(Flags() & B_SUPPORTS_LAYOUT)) {
588 			// Indeed, only if there is no label! BeOS backwards compatible
589 			// behavior:
590 			minWidth = max_c(minWidth, Bounds().Width());
591 		}
592 		*_width = minWidth;
593 	}
594 
595 	if (_height)
596 		*_height = fLayoutData->min.height;
597 }
598 
599 
600 void
601 BTextControl::ResizeToPreferred()
602 {
603 	BView::ResizeToPreferred();
604 
605 	fDivider = 0.0;
606 	const char* label = Label();
607 	if (label)
608 		fDivider = ceil(StringWidth(label)) + 2.0;
609 
610 	_LayoutTextView();
611 }
612 
613 
614 void
615 BTextControl::SetFlags(uint32 flags)
616 {
617 	// If the textview is navigable, set it to not navigable if needed
618 	// Else if it is not navigable, set it to navigable if needed
619 	if (fText->Flags() & B_NAVIGABLE) {
620 		if (!(flags & B_NAVIGABLE))
621 			fText->SetFlags(fText->Flags() & ~B_NAVIGABLE);
622 
623 	} else {
624 		if (flags & B_NAVIGABLE)
625 			fText->SetFlags(fText->Flags() | B_NAVIGABLE);
626 	}
627 
628 	// Don't make this one navigable
629 	flags &= ~B_NAVIGABLE;
630 
631 	BView::SetFlags(flags);
632 }
633 
634 
635 void
636 BTextControl::MessageReceived(BMessage *message)
637 {
638 	if (message->what == B_GET_PROPERTY || message->what == B_SET_PROPERTY) {
639 		BMessage reply(B_REPLY);
640 		bool handled = false;
641 
642 		BMessage specifier;
643 		int32 index;
644 		int32 form;
645 		const char *property;
646 		if (message->GetCurrentSpecifier(&index, &specifier, &form, &property) == B_OK) {
647 			if (strcmp(property, "Value") == 0) {
648 				if (message->what == B_GET_PROPERTY) {
649 					reply.AddString("result", fText->Text());
650 					handled = true;
651 				} else {
652 					const char *value = NULL;
653 					// B_SET_PROPERTY
654 					if (message->FindString("data", &value) == B_OK) {
655 						fText->SetText(value);
656 						reply.AddInt32("error", B_OK);
657 						handled = true;
658 					}
659 				}
660 			}
661 		}
662 
663 		if (handled) {
664 			message->SendReply(&reply);
665 			return;
666 		}
667 	}
668 
669 	BControl::MessageReceived(message);
670 }
671 
672 
673 BHandler *
674 BTextControl::ResolveSpecifier(BMessage *message, int32 index,
675 										 BMessage *specifier, int32 what,
676 										 const char *property)
677 {
678 	BPropertyInfo propInfo(sPropertyList);
679 
680 	if (propInfo.FindMatch(message, 0, specifier, what, property) >= B_OK)
681 		return this;
682 
683 	return BControl::ResolveSpecifier(message, index, specifier, what,
684 		property);
685 }
686 
687 
688 status_t
689 BTextControl::GetSupportedSuites(BMessage *data)
690 {
691 	return BControl::GetSupportedSuites(data);
692 }
693 
694 
695 void
696 BTextControl::MouseUp(BPoint pt)
697 {
698 	BControl::MouseUp(pt);
699 }
700 
701 
702 void
703 BTextControl::MouseMoved(BPoint pt, uint32 code, const BMessage *msg)
704 {
705 	BControl::MouseMoved(pt, code, msg);
706 }
707 
708 
709 void
710 BTextControl::DetachedFromWindow()
711 {
712 	BControl::DetachedFromWindow();
713 }
714 
715 
716 void
717 BTextControl::AllAttached()
718 {
719 	BControl::AllAttached();
720 }
721 
722 
723 void
724 BTextControl::AllDetached()
725 {
726 	BControl::AllDetached();
727 }
728 
729 
730 void
731 BTextControl::FrameMoved(BPoint newPosition)
732 {
733 	BControl::FrameMoved(newPosition);
734 }
735 
736 
737 void
738 BTextControl::FrameResized(float width, float height)
739 {
740 	CALLED();
741 
742 	BControl::FrameResized(width, height);
743 
744 	// TODO: this causes flickering still...
745 
746 	// changes in width
747 
748 	BRect bounds = Bounds();
749 
750 	if (bounds.Width() > fLayoutData->previous_width) {
751 		// invalidate the region between the old and the new right border
752 		BRect rect = bounds;
753 		rect.left += fLayoutData->previous_width - kFrameMargin;
754 		rect.right--;
755 		Invalidate(rect);
756 	} else if (bounds.Width() < fLayoutData->previous_width) {
757 		// invalidate the region of the new right border
758 		BRect rect = bounds;
759 		rect.left = rect.right - kFrameMargin;
760 		Invalidate(rect);
761 	}
762 
763 	// changes in height
764 
765 	if (bounds.Height() > fLayoutData->previous_height) {
766 		// invalidate the region between the old and the new bottom border
767 		BRect rect = bounds;
768 		rect.top += fLayoutData->previous_height - kFrameMargin;
769 		rect.bottom--;
770 		Invalidate(rect);
771 		// invalidate label area
772 		rect = bounds;
773 		rect.right = fDivider;
774 		Invalidate(rect);
775 	} else if (bounds.Height() < fLayoutData->previous_height) {
776 		// invalidate the region of the new bottom border
777 		BRect rect = bounds;
778 		rect.top = rect.bottom - kFrameMargin;
779 		Invalidate(rect);
780 		// invalidate label area
781 		rect = bounds;
782 		rect.right = fDivider;
783 		Invalidate(rect);
784 	}
785 
786 	fLayoutData->previous_width = bounds.Width();
787 	fLayoutData->previous_height = bounds.Height();
788 
789 	TRACE("width: %.2f, height: %.2f\n", bounds.Width(), bounds.Height());
790 }
791 
792 
793 void
794 BTextControl::WindowActivated(bool active)
795 {
796 	if (fText->IsFocus()) {
797 		// invalidate to remove/show focus indication
798 		BRect rect = fText->Frame();
799 		rect.InsetBy(-1, -1);
800 		Invalidate(rect);
801 
802 		// help out embedded text view which doesn't
803 		// get notified of this
804 		fText->Invalidate();
805 	}
806 }
807 
808 
809 BSize
810 BTextControl::MinSize()
811 {
812 	CALLED();
813 
814 	_ValidateLayoutData();
815 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
816 }
817 
818 
819 BSize
820 BTextControl::MaxSize()
821 {
822 	CALLED();
823 
824 	_ValidateLayoutData();
825 
826 	BSize max = fLayoutData->min;
827 	max.width = B_SIZE_UNLIMITED;
828 
829 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max);
830 }
831 
832 
833 BSize
834 BTextControl::PreferredSize()
835 {
836 	CALLED();
837 
838 	_ValidateLayoutData();
839 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), fLayoutData->min);
840 }
841 
842 
843 BLayoutItem*
844 BTextControl::CreateLabelLayoutItem()
845 {
846 	if (!fLayoutData->label_layout_item)
847 		fLayoutData->label_layout_item = new LabelLayoutItem(this);
848 	return fLayoutData->label_layout_item;
849 }
850 
851 
852 BLayoutItem*
853 BTextControl::CreateTextViewLayoutItem()
854 {
855 	if (!fLayoutData->text_view_layout_item)
856 		fLayoutData->text_view_layout_item = new TextViewLayoutItem(this);
857 	return fLayoutData->text_view_layout_item;
858 }
859 
860 
861 void
862 BTextControl::LayoutInvalidated(bool descendants)
863 {
864 	CALLED();
865 
866 	fLayoutData->valid = false;
867 }
868 
869 
870 void
871 BTextControl::DoLayout()
872 {
873 	// Bail out, if we shan't do layout.
874 	if (!(Flags() & B_SUPPORTS_LAYOUT))
875 		return;
876 
877 	CALLED();
878 
879 	// If the user set a layout, we let the base class version call its
880 	// hook.
881 	if (GetLayout()) {
882 		BView::DoLayout();
883 		return;
884 	}
885 
886 	_ValidateLayoutData();
887 
888 	// validate current size
889 	BSize size(Bounds().Size());
890 	if (size.width < fLayoutData->min.width)
891 		size.width = fLayoutData->min.width;
892 	if (size.height < fLayoutData->min.height)
893 		size.height = fLayoutData->min.height;
894 
895 	// divider
896 	float divider = 0;
897 	if (fLayoutData->label_layout_item && fLayoutData->text_view_layout_item) {
898 		// We have layout items. They define the divider location.
899 		divider = fLayoutData->text_view_layout_item->Frame().left
900 			- fLayoutData->label_layout_item->Frame().left;
901 	} else {
902 		if (fLayoutData->label_width > 0)
903 			divider = fLayoutData->label_width + 5;
904 	}
905 
906 	// text view
907 	BRect dirty(fText->Frame());
908 	BRect textFrame(divider + kFrameMargin, kFrameMargin,
909 		size.width - kFrameMargin, size.height - kFrameMargin);
910 
911 	// place the text view and set the divider
912 	BLayoutUtils::AlignInFrame(fText, textFrame);
913 
914 	fDivider = divider;
915 
916 	// invalidate dirty region
917 	dirty = dirty | fText->Frame();
918 	dirty.InsetBy(-kFrameMargin, -kFrameMargin);
919 
920 	Invalidate(dirty);
921 }
922 
923 
924 // #pragma mark -
925 
926 
927 status_t
928 BTextControl::Perform(perform_code code, void* _data)
929 {
930 	switch (code) {
931 		case PERFORM_CODE_MIN_SIZE:
932 			((perform_data_min_size*)_data)->return_value
933 				= BTextControl::MinSize();
934 			return B_OK;
935 		case PERFORM_CODE_MAX_SIZE:
936 			((perform_data_max_size*)_data)->return_value
937 				= BTextControl::MaxSize();
938 			return B_OK;
939 		case PERFORM_CODE_PREFERRED_SIZE:
940 			((perform_data_preferred_size*)_data)->return_value
941 				= BTextControl::PreferredSize();
942 			return B_OK;
943 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
944 			((perform_data_layout_alignment*)_data)->return_value
945 				= BTextControl::LayoutAlignment();
946 			return B_OK;
947 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
948 			((perform_data_has_height_for_width*)_data)->return_value
949 				= BTextControl::HasHeightForWidth();
950 			return B_OK;
951 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
952 		{
953 			perform_data_get_height_for_width* data
954 				= (perform_data_get_height_for_width*)_data;
955 			BTextControl::GetHeightForWidth(data->width, &data->min, &data->max,
956 				&data->preferred);
957 			return B_OK;
958 }
959 		case PERFORM_CODE_SET_LAYOUT:
960 		{
961 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
962 			BTextControl::SetLayout(data->layout);
963 			return B_OK;
964 		}
965 		case PERFORM_CODE_LAYOUT_INVALIDATED:
966 		{
967 			perform_data_layout_invalidated* data
968 				= (perform_data_layout_invalidated*)_data;
969 			BTextControl::LayoutInvalidated(data->descendants);
970 			return B_OK;
971 		}
972 		case PERFORM_CODE_DO_LAYOUT:
973 		{
974 			BTextControl::DoLayout();
975 			return B_OK;
976 		}
977 		case PERFORM_CODE_ALL_UNARCHIVED:
978 		{
979 			perform_data_all_unarchived* data
980 				= (perform_data_all_unarchived*)_data;
981 
982 			data->return_value = BTextControl::AllUnarchived(data->archive);
983 			return B_OK;
984 		}
985 		case PERFORM_CODE_ALL_ARCHIVED:
986 		{
987 			perform_data_all_archived* data
988 				= (perform_data_all_archived*)_data;
989 
990 			data->return_value = BTextControl::AllArchived(data->archive);
991 			return B_OK;
992 		}
993 	}
994 
995 	return BControl::Perform(code, _data);
996 }
997 
998 
999 void BTextControl::_ReservedTextControl1() {}
1000 void BTextControl::_ReservedTextControl2() {}
1001 void BTextControl::_ReservedTextControl3() {}
1002 void BTextControl::_ReservedTextControl4() {}
1003 
1004 
1005 BTextControl &
1006 BTextControl::operator=(const BTextControl&)
1007 {
1008 	return *this;
1009 }
1010 
1011 
1012 void
1013 BTextControl::_UpdateTextViewColors(bool enabled)
1014 {
1015 	rgb_color textColor;
1016 	rgb_color color;
1017 	BFont font;
1018 
1019 	fText->GetFontAndColor(0, &font);
1020 
1021 	if (enabled)
1022 		textColor = ui_color(B_DOCUMENT_TEXT_COLOR);
1023 	else {
1024 		textColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
1025 			B_DISABLED_LABEL_TINT);
1026 	}
1027 
1028 	fText->SetFontAndColor(&font, B_FONT_ALL, &textColor);
1029 
1030 	if (enabled) {
1031 		color = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
1032 	} else {
1033 		color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
1034 			B_LIGHTEN_2_TINT);
1035 	}
1036 
1037 	fText->SetViewColor(color);
1038 	fText->SetLowColor(color);
1039 }
1040 
1041 
1042 void
1043 BTextControl::_CommitValue()
1044 {
1045 }
1046 
1047 
1048 void
1049 BTextControl::_InitData(const char* label, const BMessage* archive)
1050 {
1051 	BRect bounds(Bounds());
1052 
1053 	fText = NULL;
1054 	fModificationMessage = NULL;
1055 	fLabelAlign = B_ALIGN_LEFT;
1056 	fDivider = 0.0f;
1057 	fLayoutData = new LayoutData(bounds.Width(), bounds.Height());
1058 
1059 	int32 flags = 0;
1060 
1061 	BFont font(be_plain_font);
1062 
1063 	if (!archive || !archive->HasString("_fname"))
1064 		flags |= B_FONT_FAMILY_AND_STYLE;
1065 
1066 	if (!archive || !archive->HasFloat("_fflt"))
1067 		flags |= B_FONT_SIZE;
1068 
1069 	if (flags != 0)
1070 		SetFont(&font, flags);
1071 
1072 	if (label)
1073 		fDivider = floorf(bounds.Width() / 2.0f);
1074 
1075 }
1076 
1077 
1078 void
1079 BTextControl::_InitText(const char* initialText, const BMessage* archive)
1080 {
1081 	if (archive)
1082 		fText = static_cast<BPrivate::_BTextInput_*>(FindView("_input_"));
1083 
1084 	if (fText == NULL) {
1085 		BRect bounds(Bounds());
1086 		BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom);
1087 		// we are stroking the frame around the text view, which
1088 		// is 2 pixels wide
1089 		frame.InsetBy(kFrameMargin, kFrameMargin);
1090 		BRect textRect(frame.OffsetToCopy(B_ORIGIN));
1091 
1092 		fText = new BPrivate::_BTextInput_(frame, textRect,
1093 			B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS
1094 			| (Flags() & B_NAVIGABLE));
1095 		AddChild(fText);
1096 
1097 		SetText(initialText);
1098 		fText->SetAlignment(B_ALIGN_LEFT);
1099 		fText->AlignTextRect();
1100 	}
1101 
1102 	// Although this is not strictly initializing the text view,
1103 	// it cannot be done while fText is NULL, so it resides here.
1104 	if (archive) {
1105 		int32 labelAlignment = B_ALIGN_LEFT;
1106 		int32 textAlignment = B_ALIGN_LEFT;
1107 
1108 		status_t err = B_OK;
1109 		if (archive->HasInt32("_a_label"))
1110 			err = archive->FindInt32("_a_label", &labelAlignment);
1111 
1112 		if (err == B_OK && archive->HasInt32("_a_text"))
1113 			err = archive->FindInt32("_a_text", &textAlignment);
1114 
1115 		SetAlignment((alignment)labelAlignment, (alignment)textAlignment);
1116 	}
1117 
1118 	uint32 navigableFlags = Flags() & B_NAVIGABLE;
1119 	if (navigableFlags != 0)
1120 		BView::SetFlags(Flags() & ~B_NAVIGABLE);
1121 }
1122 
1123 
1124 void
1125 BTextControl::_ValidateLayout()
1126 {
1127 	CALLED();
1128 
1129 	_ValidateLayoutData();
1130 
1131 	ResizeTo(Bounds().Width(), fLayoutData->min.height);
1132 
1133 	_LayoutTextView();
1134 }
1135 
1136 
1137 void
1138 BTextControl::_LayoutTextView()
1139 {
1140 	CALLED();
1141 
1142 	BRect frame = Bounds();
1143 	frame.left = fDivider;
1144 	// we are stroking the frame around the text view, which
1145 	// is 2 pixels wide
1146 	frame.InsetBy(kFrameMargin, kFrameMargin);
1147 	fText->MoveTo(frame.left, frame.top);
1148 	fText->ResizeTo(frame.Width(), frame.Height());
1149 	fText->AlignTextRect();
1150 
1151 	TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height());
1152 	TRACE("fDivider: %.2f\n", fDivider);
1153 	TRACE("fText frame: (%.2f, %.2f, %.2f, %.2f)\n",
1154 		frame.left, frame.top, frame.right, frame.bottom);
1155 }
1156 
1157 
1158 void
1159 BTextControl::_UpdateFrame()
1160 {
1161 	CALLED();
1162 
1163 	if (fLayoutData->label_layout_item && fLayoutData->text_view_layout_item) {
1164 		BRect labelFrame = fLayoutData->label_layout_item->Frame();
1165 		BRect textFrame = fLayoutData->text_view_layout_item->Frame();
1166 
1167 		// update divider
1168 		fDivider = textFrame.left - labelFrame.left;
1169 
1170 		MoveTo(labelFrame.left, labelFrame.top);
1171 		BSize oldSize = Bounds().Size();
1172 		ResizeTo(textFrame.left + textFrame.Width() - labelFrame.left,
1173 			textFrame.top + textFrame.Height() - labelFrame.top);
1174 		BSize newSize = Bounds().Size();
1175 
1176 		// If the size changes, ResizeTo() will trigger a relayout, otherwise
1177 		// we need to do that explicitly.
1178 		if (newSize != oldSize)
1179 			Relayout();
1180 	}
1181 }
1182 
1183 
1184 void
1185 BTextControl::_ValidateLayoutData()
1186 {
1187 	CALLED();
1188 
1189 	if (fLayoutData->valid)
1190 		return;
1191 
1192 	// cache font height
1193 	font_height& fh = fLayoutData->font_info;
1194 	GetFontHeight(&fh);
1195 
1196 	if (Label() != NULL) {
1197 		fLayoutData->label_width = ceilf(StringWidth(Label()));
1198 		fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent);
1199 	} else {
1200 		fLayoutData->label_width = 0;
1201 		fLayoutData->label_height = 0;
1202 	}
1203 
1204 	// compute the minimal divider
1205 	float divider = 0;
1206 	if (fLayoutData->label_width > 0)
1207 		divider = fLayoutData->label_width + 5;
1208 
1209 	// If we shan't do real layout, we let the current divider take influence.
1210 	if (!(Flags() & B_SUPPORTS_LAYOUT))
1211 		divider = max_c(divider, fDivider);
1212 
1213 	// get the minimal (== preferred) text view size
1214 	fLayoutData->text_view_min = fText->MinSize();
1215 
1216 	TRACE("text view min width: %.2f\n", fLayoutData->text_view_min.width);
1217 
1218 	// compute our minimal (== preferred) size
1219 	BSize min(fLayoutData->text_view_min);
1220 	min.width += 2 * kFrameMargin;
1221 	min.height += 2 * kFrameMargin;
1222 
1223 	if (divider > 0)
1224 		min.width += divider;
1225 	if (fLayoutData->label_height > min.height)
1226 		min.height = fLayoutData->label_height;
1227 
1228 	fLayoutData->min = min;
1229 
1230 	fLayoutData->valid = true;
1231 	ResetLayoutInvalidation();
1232 
1233 	TRACE("width: %.2f, height: %.2f\n", min.width, min.height);
1234 }
1235 
1236 
1237 // #pragma mark -
1238 
1239 
1240 BTextControl::LabelLayoutItem::LabelLayoutItem(BTextControl* parent)
1241 	:
1242 	fParent(parent),
1243 	fFrame()
1244 {
1245 }
1246 
1247 
1248 BTextControl::LabelLayoutItem::LabelLayoutItem(BMessage* from)
1249 	:
1250 	BAbstractLayoutItem(from),
1251 	fParent(NULL),
1252 	fFrame()
1253 {
1254 	from->FindRect(kFrameField, &fFrame);
1255 }
1256 
1257 
1258 bool
1259 BTextControl::LabelLayoutItem::IsVisible()
1260 {
1261 	return !fParent->IsHidden(fParent);
1262 }
1263 
1264 
1265 void
1266 BTextControl::LabelLayoutItem::SetVisible(bool visible)
1267 {
1268 	// not allowed
1269 }
1270 
1271 
1272 BRect
1273 BTextControl::LabelLayoutItem::Frame()
1274 {
1275 	return fFrame;
1276 }
1277 
1278 
1279 void
1280 BTextControl::LabelLayoutItem::SetFrame(BRect frame)
1281 {
1282 	fFrame = frame;
1283 	fParent->_UpdateFrame();
1284 }
1285 
1286 
1287 void
1288 BTextControl::LabelLayoutItem::SetParent(BTextControl* parent)
1289 {
1290 	fParent = parent;
1291 }
1292 
1293 
1294 BView*
1295 BTextControl::LabelLayoutItem::View()
1296 {
1297 	return fParent;
1298 }
1299 
1300 
1301 BSize
1302 BTextControl::LabelLayoutItem::BaseMinSize()
1303 {
1304 	fParent->_ValidateLayoutData();
1305 
1306 	if (!fParent->Label())
1307 		return BSize(-1, -1);
1308 
1309 	return BSize(fParent->fLayoutData->label_width + 5,
1310 		fParent->fLayoutData->label_height);
1311 }
1312 
1313 
1314 BSize
1315 BTextControl::LabelLayoutItem::BaseMaxSize()
1316 {
1317 	return BaseMinSize();
1318 }
1319 
1320 
1321 BSize
1322 BTextControl::LabelLayoutItem::BasePreferredSize()
1323 {
1324 	return BaseMinSize();
1325 }
1326 
1327 
1328 BAlignment
1329 BTextControl::LabelLayoutItem::BaseAlignment()
1330 {
1331 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1332 }
1333 
1334 
1335 status_t
1336 BTextControl::LabelLayoutItem::Archive(BMessage* into, bool deep) const
1337 {
1338 	BArchiver archiver(into);
1339 	status_t err = BAbstractLayoutItem::Archive(into, deep);
1340 	if (err == B_OK)
1341 		err = into->AddRect(kFrameField, fFrame);
1342 
1343 	return archiver.Finish(err);
1344 }
1345 
1346 
1347 BArchivable*
1348 BTextControl::LabelLayoutItem::Instantiate(BMessage* from)
1349 {
1350 	if (validate_instantiation(from, "BTextControl::LabelLayoutItem"))
1351 		return new LabelLayoutItem(from);
1352 	return NULL;
1353 }
1354 
1355 
1356 // #pragma mark -
1357 
1358 
1359 BTextControl::TextViewLayoutItem::TextViewLayoutItem(BTextControl* parent)
1360 	:
1361 	fParent(parent),
1362 	fFrame()
1363 {
1364 	// by default the part right of the divider shall have an unlimited maximum
1365 	// width
1366 	SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
1367 }
1368 
1369 
1370 BTextControl::TextViewLayoutItem::TextViewLayoutItem(BMessage* from)
1371 	:
1372 	BAbstractLayoutItem(from),
1373 	fParent(NULL),
1374 	fFrame()
1375 {
1376 	from->FindRect(kFrameField, &fFrame);
1377 }
1378 
1379 
1380 bool
1381 BTextControl::TextViewLayoutItem::IsVisible()
1382 {
1383 	return !fParent->IsHidden(fParent);
1384 }
1385 
1386 
1387 void
1388 BTextControl::TextViewLayoutItem::SetVisible(bool visible)
1389 {
1390 	// not allowed
1391 }
1392 
1393 
1394 BRect
1395 BTextControl::TextViewLayoutItem::Frame()
1396 {
1397 	return fFrame;
1398 }
1399 
1400 
1401 void
1402 BTextControl::TextViewLayoutItem::SetFrame(BRect frame)
1403 {
1404 	fFrame = frame;
1405 	fParent->_UpdateFrame();
1406 }
1407 
1408 
1409 void
1410 BTextControl::TextViewLayoutItem::SetParent(BTextControl* parent)
1411 {
1412 	fParent = parent;
1413 }
1414 
1415 
1416 BView*
1417 BTextControl::TextViewLayoutItem::View()
1418 {
1419 	return fParent;
1420 }
1421 
1422 
1423 BSize
1424 BTextControl::TextViewLayoutItem::BaseMinSize()
1425 {
1426 	fParent->_ValidateLayoutData();
1427 
1428 	BSize size = fParent->fLayoutData->text_view_min;
1429 	size.width += 2 * kFrameMargin;
1430 	size.height += 2 * kFrameMargin;
1431 
1432 	return size;
1433 }
1434 
1435 
1436 BSize
1437 BTextControl::TextViewLayoutItem::BaseMaxSize()
1438 {
1439 	BSize size(BaseMinSize());
1440 	size.width = B_SIZE_UNLIMITED;
1441 	return size;
1442 }
1443 
1444 
1445 BSize
1446 BTextControl::TextViewLayoutItem::BasePreferredSize()
1447 {
1448 	BSize size(BaseMinSize());
1449 	// puh, no idea...
1450 	size.width = 100;
1451 	return size;
1452 }
1453 
1454 
1455 BAlignment
1456 BTextControl::TextViewLayoutItem::BaseAlignment()
1457 {
1458 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1459 }
1460 
1461 
1462 status_t
1463 BTextControl::TextViewLayoutItem::Archive(BMessage* into, bool deep) const
1464 {
1465 	BArchiver archiver(into);
1466 	status_t err = BAbstractLayoutItem::Archive(into, deep);
1467 	if (err == B_OK)
1468 		err = into->AddRect(kFrameField, fFrame);
1469 
1470 	return archiver.Finish(err);
1471 }
1472 
1473 
1474 BArchivable*
1475 BTextControl::TextViewLayoutItem::Instantiate(BMessage* from)
1476 {
1477 	if (validate_instantiation(from, "BTextControl::TextViewLayoutItem"))
1478 		return new TextViewLayoutItem(from);
1479 	return NULL;
1480 }
1481 
1482 
1483 #if __GNUC__ == 2
1484 
1485 
1486 extern "C" void
1487 InvalidateLayout__12BTextControlb(BView* view, bool descendants)
1488 {
1489 	perform_data_layout_invalidated data;
1490 	data.descendants = descendants;
1491 
1492 	view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);
1493 }
1494 
1495 
1496 #endif
1497