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