xref: /haiku/src/kits/interface/TextControl.cpp (revision d3d8b26997fac34a84981e6d2b649521de2cc45a)
1 /*
2  * Copyright 2001-2005, 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  */
9 
10 /**	BTextControl displays text that can act like a control. */
11 
12 
13 #include <stdio.h>
14 
15 #include <Message.h>
16 #include <Region.h>
17 #include <TextControl.h>
18 #include <Window.h>
19 
20 #include "TextInput.h"
21 
22 
23 BTextControl::BTextControl(BRect frame, const char *name, const char *label,
24 						   const char *text, BMessage *message, uint32 mask,
25 						   uint32 flags)
26 	: BControl(frame, name, label, message, mask, flags | B_FRAME_EVENTS)
27 {
28 	_InitData(label, text);
29 
30 	float height;
31 	BTextControl::GetPreferredSize(NULL, &height);
32 
33 	ResizeTo(Bounds().Width(), height);
34 
35 	float lineHeight = ceil(fText->LineHeight(0));
36 	fText->ResizeTo(fText->Bounds().Width(), lineHeight);
37 	fText->MoveTo(fText->Frame().left, (height - lineHeight) / 2);
38 
39 	fPreviousHeight = Bounds().Height();
40 }
41 
42 
43 BTextControl::~BTextControl()
44 {
45 	SetModificationMessage(NULL);
46 }
47 
48 
49 BTextControl::BTextControl(BMessage* archive)
50 	: BControl(archive)
51 {
52 	_InitData(Label(), NULL, archive);
53 
54 	int32 labelAlignment = B_ALIGN_LEFT;
55 	int32 textAlignment = B_ALIGN_LEFT;
56 
57 	if (archive->HasInt32("_a_label"))
58 		archive->FindInt32("_a_label", &labelAlignment);
59 
60 	if (archive->HasInt32("_a_text"))
61 		archive->FindInt32("_a_text", &textAlignment);
62 
63 	SetAlignment((alignment)labelAlignment, (alignment)textAlignment);
64 
65 	if (archive->HasFloat("_divide"))
66 		archive->FindFloat("_a_text", &fDivider);
67 
68 	if (archive->HasMessage("_mod_msg")) {
69 		BMessage* message = new BMessage;
70 		archive->FindMessage("_mod_msg", message);
71 		SetModificationMessage(message);
72 	}
73 }
74 
75 
76 BArchivable *
77 BTextControl::Instantiate(BMessage *archive)
78 {
79 	if (validate_instantiation(archive, "BTextControl"))
80 		return new BTextControl(archive);
81 	else
82 		return NULL;
83 }
84 
85 
86 status_t
87 BTextControl::Archive(BMessage *data, bool deep) const
88 {
89 	status_t ret = BView::Archive(data, deep);
90 	alignment labelAlignment, textAlignment;
91 
92 	GetAlignment(&labelAlignment, &textAlignment);
93 
94 	if (ret == B_OK)
95 		ret = data->AddInt32("_a_label", labelAlignment);
96 	if (ret == B_OK)
97 		ret = data->AddInt32("_a_text", textAlignment);
98 	if (ret == B_OK)
99 		ret = data->AddFloat("_divide", Divider());
100 
101 	if (ModificationMessage() && (ret == B_OK))
102 		ret = data->AddMessage("_mod_msg", ModificationMessage());
103 
104 	return ret;
105 }
106 
107 
108 void
109 BTextControl::SetText(const char *text)
110 {
111 	if (InvokeKind() != B_CONTROL_INVOKED)
112 		return;
113 
114 	fText->SetText(text);
115 
116 	if (IsFocus())
117 		fText->SetInitialText();
118 
119 	fText->Invalidate();
120 }
121 
122 
123 const char *
124 BTextControl::Text() const
125 {
126 	return fText->Text();
127 }
128 
129 
130 void
131 BTextControl::SetValue(int32 value)
132 {
133 	BControl::SetValue(value);
134 }
135 
136 
137 status_t
138 BTextControl::Invoke(BMessage *message)
139 {
140 	return BControl::Invoke(message);
141 }
142 
143 
144 BTextView *
145 BTextControl::TextView() const
146 {
147 	return fText;
148 }
149 
150 
151 void
152 BTextControl::SetModificationMessage(BMessage *message)
153 {
154 	delete fModificationMessage;
155 	fModificationMessage = message;
156 }
157 
158 
159 BMessage *
160 BTextControl::ModificationMessage() const
161 {
162 	return fModificationMessage;
163 }
164 
165 
166 void
167 BTextControl::SetAlignment(alignment labelAlignment, alignment textAlignment)
168 {
169 	fText->SetAlignment(textAlignment);
170 	fText->AlignTextRect();
171 
172 	if (fLabelAlign != labelAlignment) {
173 		fLabelAlign = labelAlignment;
174 		Invalidate();
175 	}
176 }
177 
178 
179 void
180 BTextControl::GetAlignment(alignment* _label, alignment* _text) const
181 {
182 	if (_label)
183 		*_label = fLabelAlign;
184 	if (_text)
185 		*_text = fText->Alignment();
186 }
187 
188 
189 void
190 BTextControl::SetDivider(float dividingLine)
191 {
192 	dividingLine = floorf(dividingLine + 0.5);
193 
194 	float dx = fDivider - dividingLine;
195 
196 	fDivider = dividingLine;
197 
198 	fText->MoveBy(-dx, 0.0f);
199 	fText->ResizeBy(dx, 0.0f);
200 
201 	if (Window()) {
202 		fText->Invalidate();
203 		Invalidate();
204 	}
205 }
206 
207 
208 float
209 BTextControl::Divider() const
210 {
211 	return fDivider;
212 }
213 
214 
215 void
216 BTextControl::Draw(BRect updateRect)
217 {
218 	rgb_color noTint = ui_color(B_PANEL_BACKGROUND_COLOR);
219 	rgb_color lighten1 = tint_color(noTint, B_LIGHTEN_1_TINT);
220 	rgb_color lighten2 = tint_color(noTint, B_LIGHTEN_2_TINT);
221 	rgb_color lightenMax = tint_color(noTint, B_LIGHTEN_MAX_TINT);
222 	rgb_color darken1 = tint_color(noTint, B_DARKEN_1_TINT);
223 	rgb_color darken2 = tint_color(noTint, B_DARKEN_2_TINT);
224 	rgb_color darken4 = tint_color(noTint, B_DARKEN_4_TINT);
225 	rgb_color navigationColor = ui_color(B_KEYBOARD_NAVIGATION_COLOR);
226 
227 	bool enabled = IsEnabled();
228 	bool active = false;
229 
230 	if (fText->IsFocus() && Window()->IsActive())
231 		active = true;
232 
233 	// outer bevel
234 
235 	BRect rect = Bounds();
236 	rect.left = fDivider;
237 
238 	if (enabled)
239 		SetHighColor(darken1);
240 	else
241 		SetHighColor(noTint);
242 
243 	StrokeLine(rect.LeftBottom(), rect.LeftTop());
244 	StrokeLine(rect.RightTop());
245 
246 	if (enabled)
247 		SetHighColor(lighten2);
248 	else
249 		SetHighColor(lighten1);
250 
251 	StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom());
252 	StrokeLine(BPoint(rect.right, rect.top + 1.0f), rect.RightBottom());
253 
254 	// inner bevel
255 
256 	rect.InsetBy(1.0f, 1.0f);
257 
258 	if (active) {
259 		SetHighColor(navigationColor);
260 		StrokeRect(rect);
261 	} else {
262 		if (enabled)
263 			SetHighColor(darken4);
264 		else
265 			SetHighColor(darken2);
266 
267 		StrokeLine(rect.LeftTop(), rect.LeftBottom());
268 		StrokeLine(rect.LeftTop(), rect.RightTop());
269 
270 		SetHighColor(noTint);
271 		StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom());
272 		StrokeLine(BPoint(rect.right, rect.top + 1.0f));
273 	}
274 
275 	// label
276 
277 	if (Label()) {
278 		font_height fontHeight;
279 		GetFontHeight(&fontHeight);
280 
281 		float y = fontHeight.ascent + fText->Frame().top + 1;
282 		float x;
283 
284 		float labelWidth = StringWidth(Label());
285 		switch (fLabelAlign) {
286 			case B_ALIGN_RIGHT:
287 				x = fDivider - labelWidth - 3.0f;
288 				break;
289 
290 			case B_ALIGN_CENTER:
291 				x = fDivider - labelWidth / 2.0f;
292 				break;
293 
294 			default:
295 				x = 3.0f;
296 				break;
297 		}
298 
299 		BRect labelArea(x, fText->Frame().top, x + labelWidth,
300 				ceilf(fontHeight.ascent + fontHeight.descent) + 1);
301 		if (x < fDivider && updateRect.Intersects(labelArea)) {
302 			labelArea.right = fDivider;
303 
304 			BRegion clipRegion(labelArea);
305 			ConstrainClippingRegion(&clipRegion);
306 			SetHighColor(IsEnabled() ? ui_color(B_CONTROL_TEXT_COLOR)
307 				: tint_color(noTint, B_DISABLED_LABEL_TINT));
308 			DrawString(Label(), BPoint(x, y));
309 		}
310 	}
311 }
312 
313 
314 void
315 BTextControl::MouseDown(BPoint where)
316 {
317 	if (!fText->IsFocus()) {
318 		fText->MakeFocus(true);
319 		fText->SelectAll();
320 	}
321 }
322 
323 
324 void
325 BTextControl::AttachedToWindow()
326 {
327 	BControl::AttachedToWindow();
328 
329 	_UpdateTextViewColors(IsEnabled());
330 	fText->MakeEditable(IsEnabled());
331 }
332 
333 
334 void
335 BTextControl::MakeFocus(bool state)
336 {
337 	if (state != fText->IsFocus()) {
338 		fText->MakeFocus(state);
339 
340 		if (state)
341 			fText->SelectAll();
342 	}
343 }
344 
345 
346 void
347 BTextControl::SetEnabled(bool enabled)
348 {
349 	if (IsEnabled() == enabled)
350 		return;
351 
352 	if (Window()) {
353 		fText->MakeEditable(enabled);
354 
355 		_UpdateTextViewColors(enabled);
356 
357 		fText->Invalidate();
358 		Window()->UpdateIfNeeded();
359 	}
360 
361 	BControl::SetEnabled(enabled);
362 }
363 
364 
365 void
366 BTextControl::GetPreferredSize(float *_width, float *_height)
367 {
368 	if (_height) {
369 		// we need enough space for the label and the child text view
370 		font_height fontHeight;
371 		GetFontHeight(&fontHeight);
372 		float labelHeight = ceil(fontHeight.ascent + fontHeight.descent
373 			+ fontHeight.leading);
374 		float textHeight = fText->LineHeight(0) + 4.0;
375 
376 		*_height = max_c(labelHeight, textHeight);
377 	}
378 
379 	if (_width) {
380 		// TODO: this one I need to find out
381 		float width = 20.0f + ceilf(StringWidth(Label()));
382 		if (width < Bounds().Width())
383 			width = Bounds().Width();
384 		*_width = width;
385 	}
386 }
387 
388 
389 void
390 BTextControl::ResizeToPreferred()
391 {
392 	// TODO: change divider?
393 	BView::ResizeToPreferred();
394 }
395 
396 
397 void
398 BTextControl::SetFlags(uint32 flags)
399 {
400 	if (!fSkipSetFlags) {
401 		// If the textview is navigable, set it to not navigable if needed
402 		// Else if it is not navigable, set it to navigable if needed
403 		if (fText->Flags() & B_NAVIGABLE) {
404 			if (!(flags & B_NAVIGABLE))
405 				fText->SetFlags(fText->Flags() & ~B_NAVIGABLE);
406 
407 		} else {
408 			if (flags & B_NAVIGABLE)
409 				fText->SetFlags(fText->Flags() | B_NAVIGABLE);
410 		}
411 
412 		// Don't make this one navigable
413 		flags &= ~B_NAVIGABLE;
414 	}
415 
416 	BView::SetFlags(flags);
417 }
418 
419 
420 void
421 BTextControl::MessageReceived(BMessage *msg)
422 {
423 	switch(msg->what) {
424 		case B_SET_PROPERTY:
425 		case B_GET_PROPERTY:
426 			// TODO
427 			break;
428 		default:
429 			BControl::MessageReceived(msg);
430 			break;
431 	}
432 }
433 
434 
435 BHandler *
436 BTextControl::ResolveSpecifier(BMessage *msg, int32 index,
437 										 BMessage *specifier, int32 form,
438 										 const char *property)
439 {
440 	/*
441 	BPropertyInfo propInfo(prop_list);
442 	BHandler *target = NULL;
443 
444 	if (propInfo.FindMatch(message, 0, specifier, what, property) < B_OK)
445 		return BControl::ResolveSpecifier(message, index, specifier, what,
446 			property);
447 	else
448 		return this;
449 	*/
450 	return BControl::ResolveSpecifier(msg, index, specifier, form, property);
451 }
452 
453 
454 status_t
455 BTextControl::GetSupportedSuites(BMessage *data)
456 {
457 	return BControl::GetSupportedSuites(data);
458 }
459 
460 
461 void
462 BTextControl::MouseUp(BPoint pt)
463 {
464 	BControl::MouseUp(pt);
465 }
466 
467 
468 void
469 BTextControl::MouseMoved(BPoint pt, uint32 code, const BMessage *msg)
470 {
471 	BControl::MouseMoved(pt, code, msg);
472 }
473 
474 
475 void
476 BTextControl::DetachedFromWindow()
477 {
478 	BControl::DetachedFromWindow();
479 }
480 
481 
482 void
483 BTextControl::AllAttached()
484 {
485 	BControl::AllAttached();
486 }
487 
488 
489 void
490 BTextControl::AllDetached()
491 {
492 	BControl::AllDetached();
493 }
494 
495 
496 void
497 BTextControl::FrameMoved(BPoint newPosition)
498 {
499 	BControl::FrameMoved(newPosition);
500 }
501 
502 
503 void
504 BTextControl::FrameResized(float width, float height)
505 {
506 	BControl::FrameResized(width, height);
507 
508 	// changes in width
509 
510 	BRect bounds = Bounds();
511 	const float border = 2.0f;
512 
513 	if (bounds.Width() > fPreviousWidth) {
514 		// invalidate the region between the old and the new right border
515 		BRect rect = bounds;
516 		rect.left += fPreviousWidth - border;
517 		rect.right--;
518 		Invalidate(rect);
519 	} else if (bounds.Width() < fPreviousWidth) {
520 		// invalidate the region of the new right border
521 		BRect rect = bounds;
522 		rect.left = rect.right - border;
523 		Invalidate(rect);
524 	}
525 
526 	// changes in height
527 
528 	if (bounds.Height() > fPreviousHeight) {
529 		// invalidate the region between the old and the new bottom border
530 		BRect rect = bounds;
531 		rect.top += fPreviousHeight - border;
532 		rect.bottom--;
533 		Invalidate(rect);
534 	} else if (bounds.Height() < fPreviousHeight) {
535 		// invalidate the region of the new bottom border
536 		BRect rect = bounds;
537 		rect.top = rect.bottom - border;
538 		Invalidate(rect);
539 	}
540 
541 	fPreviousWidth = uint16(bounds.Width());
542 	fPreviousHeight = uint16(bounds.Height());
543 }
544 
545 
546 void
547 BTextControl::WindowActivated(bool active)
548 {
549 	if (fText->IsFocus()) {
550 		// invalidate to remove/show focus indication
551 		BRect rect = Bounds();
552 		rect.left = fDivider;
553 		Invalidate(rect);
554 
555 		// help out embedded text view which doesn't
556 		// get notified of this
557 		fText->Invalidate();
558 	}
559 }
560 
561 
562 status_t
563 BTextControl::Perform(perform_code d, void *arg)
564 {
565 	return BControl::Perform(d, arg);
566 }
567 
568 
569 void BTextControl::_ReservedTextControl1() {}
570 void BTextControl::_ReservedTextControl2() {}
571 void BTextControl::_ReservedTextControl3() {}
572 void BTextControl::_ReservedTextControl4() {}
573 
574 
575 BTextControl &
576 BTextControl::operator=(const BTextControl&)
577 {
578 	return *this;
579 }
580 
581 
582 void
583 BTextControl::_UpdateTextViewColors(bool enabled)
584 {
585 	rgb_color textColor;
586 	rgb_color color;
587 	BFont font;
588 
589 	fText->GetFontAndColor(0, &font);
590 
591 	if (enabled)
592 		textColor = ui_color(B_DOCUMENT_TEXT_COLOR);
593 	else {
594 		textColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
595 			B_DISABLED_LABEL_TINT);
596 	}
597 
598 	fText->SetFontAndColor(&font, B_FONT_ALL, &textColor);
599 
600 	if (enabled) {
601 		color = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
602 	} else {
603 		color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
604 			B_LIGHTEN_2_TINT);
605 	}
606 
607 	fText->SetViewColor(color);
608 	fText->SetLowColor(color);
609 }
610 
611 
612 void
613 BTextControl::_CommitValue()
614 {
615 }
616 
617 
618 void
619 BTextControl::_InitData(const char* label, const char* initialText,
620 	BMessage* archive)
621 {
622 	BRect bounds(Bounds());
623 
624 	fText = NULL;
625 	fModificationMessage = NULL;
626 	fLabelAlign = B_ALIGN_LEFT;
627 	fDivider = 0.0f;
628 	fPreviousWidth = bounds.Width();
629 	fPreviousHeight = bounds.Height();
630 	fSkipSetFlags = false;
631 
632 	int32 flags = 0;
633 
634 	BFont font(be_plain_font);
635 
636 	if (!archive || !archive->HasString("_fname"))
637 		flags |= B_FONT_FAMILY_AND_STYLE;
638 
639 	if (!archive || !archive->HasFloat("_fflt"))
640 		flags |= B_FONT_SIZE;
641 
642 	if (flags != 0)
643 		SetFont(&font, flags);
644 
645 	if (label)
646 		fDivider = floorf(bounds.Width() / 2.0f);
647 
648 	uint32 navigableFlags = Flags() & B_NAVIGABLE;
649 	if (navigableFlags != 0) {
650 		fSkipSetFlags = true;
651 		SetFlags(Flags() & ~B_NAVIGABLE);
652 		fSkipSetFlags = false;
653 	}
654 
655 	if (archive)
656 		fText = static_cast<_BTextInput_ *>(FindView("_input_"));
657 	else {
658 		BRect frame(fDivider, bounds.top,
659 					bounds.right, bounds.bottom);
660 		// we are stroking the frame around the text view, which
661 		// is 2 pixels wide
662 		frame.InsetBy(2.0, 3.0);
663 		BRect textRect(frame.OffsetToCopy(B_ORIGIN));
664 
665 		fText = new _BTextInput_(frame, textRect,
666 			B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP,
667 			B_WILL_DRAW | B_FRAME_EVENTS | navigableFlags);
668 		AddChild(fText);
669 
670 		SetText(initialText);
671 		fText->SetAlignment(B_ALIGN_LEFT);
672 		fText->AlignTextRect();
673 	}
674 }
675