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