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