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