xref: /haiku/src/kits/interface/TextControl.cpp (revision fef6144999c2fa611f59ee6ffe6dd7999501385c)
1 //------------------------------------------------------------------------------
2 //	Copyright (c) 2001-2004, Haiku, Inc.
3 //
4 //	Permission is hereby granted, free of charge, to any person obtaining a
5 //	copy of this software and associated documentation files (the "Software"),
6 //	to deal in the Software without restriction, including without limitation
7 //	the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 //	and/or sell copies of the Software, and to permit persons to whom the
9 //	Software is furnished to do so, subject to the following conditions:
10 //
11 //	The above copyright notice and this permission notice shall be included in
12 //	all copies or substantial portions of the Software.
13 //
14 //	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 //	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 //	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 //	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 //	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 //	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 //	DEALINGS IN THE SOFTWARE.
21 //
22 //	File Name:		TextControl.cpp
23 //	Author:			Frans van Nispen (xlr8@tref.nl)
24 //	Description:	BTextControl displays text that can act like a control.
25 //------------------------------------------------------------------------------
26 #include <stdio.h>
27 
28 #include <Message.h>
29 #include <TextControl.h>
30 #include <Window.h>
31 
32 #include "TextInput.h"
33 
34 
35 BTextControl::BTextControl(BRect frame, const char *name, const char *label,
36 						   const char *text, BMessage *message, uint32 mask,
37 						   uint32 flags)
38 	:	BControl(frame, name, label, message, mask, flags | B_FRAME_EVENTS)
39 {
40 	InitData(label, text);
41 
42 	float lineHeight = fText->LineHeight(0);
43 
44 	ResizeTo(Bounds().Width(), lineHeight + 8);
45 
46 	fText->ResizeTo(fText->Bounds().Width(), lineHeight + 4);
47 	fText->MoveTo(fText->Frame().left, 2.0f);
48 }
49 
50 
51 BTextControl::~BTextControl()
52 {
53 	SetModificationMessage(NULL);
54 }
55 
56 
57 BTextControl::BTextControl(BMessage *data)
58 	:	BControl(data)
59 {
60 	InitData(Label(), NULL, data);
61 
62 	int32 _a_label = B_ALIGN_LEFT;
63 	int32 _a_text = B_ALIGN_LEFT;
64 
65 	if (data->HasInt32("_a_label"))
66 		data->FindInt32("_a_label", &_a_label);
67 
68 	if (data->HasInt32("_a_text"))
69 		data->FindInt32("_a_text", &_a_text);
70 
71 	SetAlignment((alignment)_a_label, (alignment)_a_text);
72 
73 	if (data->HasFloat("_divide"))
74 		data->FindFloat("_a_text", &fDivider);
75 
76 	if (data->HasMessage("_mod_msg")) {
77 		BMessage *_mod_msg = new BMessage;
78 		data->FindMessage("_mod_msg", _mod_msg);
79 		SetModificationMessage(_mod_msg);
80 	}
81 }
82 
83 
84 BArchivable *
85 BTextControl::Instantiate(BMessage *archive)
86 {
87 	if (validate_instantiation(archive, "BTextControl"))
88 		return new BTextControl(archive);
89 	else
90 		return NULL;
91 }
92 
93 
94 status_t
95 BTextControl::Archive(BMessage *data, bool deep) const
96 {
97 	BView::Archive(data, deep);
98 
99 	alignment labelAlignment, textAlignment;
100 
101 	GetAlignment(&labelAlignment, &textAlignment);
102 
103 	data->AddInt32("_a_label", labelAlignment);
104 	data->AddInt32("_a_text", textAlignment);
105 	data->AddFloat("_divide", Divider());
106 
107 	if (ModificationMessage())
108 		data->AddMessage("_mod_msg", ModificationMessage());
109 
110 	return B_OK;
111 }
112 
113 
114 void
115 BTextControl::SetText(const char *text)
116 {
117 	if (InvokeKind() != B_CONTROL_INVOKED)
118 		return;
119 
120 	fText->SetText(text);
121 
122 	if (IsFocus())
123 		fText->SetInitialText();
124 
125 	fText->Invalidate();
126 }
127 
128 
129 const char *
130 BTextControl::Text() const
131 {
132 	return fText->Text();
133 }
134 
135 
136 void
137 BTextControl::SetValue(int32 value)
138 {
139 	BControl::SetValue(value);
140 }
141 
142 
143 status_t
144 BTextControl::Invoke(BMessage *message)
145 {
146 	return BControl::Invoke(message);
147 }
148 
149 
150 BTextView *
151 BTextControl::TextView() const
152 {
153 	return fText;
154 }
155 
156 
157 void
158 BTextControl::SetModificationMessage(BMessage *message)
159 {
160 	delete fModificationMessage;
161 	fModificationMessage = message;
162 }
163 
164 
165 BMessage *
166 BTextControl::ModificationMessage() const
167 {
168 	return fModificationMessage;
169 }
170 
171 
172 void
173 BTextControl::SetAlignment(alignment labelAlignment, alignment textAlignment)
174 {
175 	fText->SetAlignment(textAlignment);
176 	fText->AlignTextRect();
177 
178 	if (fLabelAlign != labelAlignment) {
179 		fLabelAlign = labelAlignment;
180 		Invalidate();
181 	}
182 }
183 
184 
185 void
186 BTextControl::GetAlignment(alignment *label, alignment *text) const
187 {
188 	if (label)
189 		*label = fLabelAlign;
190 	if (text)
191 		*text = fText->Alignment();
192 }
193 
194 
195 void
196 BTextControl::SetDivider(float dividingLine)
197 {
198 	dividingLine = floorf(dividingLine + 0.5);
199 
200 	float dx = fDivider - dividingLine;
201 
202 	fDivider = dividingLine;
203 
204 	fText->MoveBy(-dx, 0.0f);
205 	fText->ResizeBy(dx, 0.0f);
206 
207 	if (Window()) {
208 		fText->Invalidate();
209 		Invalidate();
210 	}
211 }
212 
213 
214 float
215 BTextControl::Divider() const
216 {
217 	return fDivider;
218 }
219 
220 
221 void
222 BTextControl::Draw(BRect updateRect)
223 {
224 	rgb_color no_tint = ui_color(B_PANEL_BACKGROUND_COLOR),
225 	lighten1 = tint_color(no_tint, B_LIGHTEN_1_TINT),
226 	lighten2 = tint_color(no_tint, B_LIGHTEN_2_TINT),
227 	lightenmax = tint_color(no_tint, B_LIGHTEN_MAX_TINT),
228 	darken1 = tint_color(no_tint, B_DARKEN_1_TINT),
229 	darken2 = tint_color(no_tint, B_DARKEN_2_TINT),
230 	darken4 = tint_color(no_tint, B_DARKEN_4_TINT),
231 	darkenmax = tint_color(no_tint, B_DARKEN_MAX_TINT),
232 	nav = ui_color(B_KEYBOARD_NAVIGATION_COLOR);
233 
234 	bool enabled = IsEnabled();
235 	bool active = false;
236 
237 	if (fText->IsFocus() && Window()->IsActive())
238 		active = true;
239 
240 	BRect rect(fText->Frame());
241 	rect.InsetBy(-1.0f, -1.0f);
242 
243 	if (active) {
244 		SetHighColor(nav);
245 		StrokeRect(rect);
246 
247 	} else {
248 		if (enabled)
249 			SetHighColor(darken4);
250 		else
251 			SetHighColor(darken2);
252 
253 		StrokeLine(rect.LeftTop(), rect.LeftBottom());
254 		StrokeLine(rect.LeftTop(), rect.RightTop());
255 
256 		SetHighColor(no_tint);
257 		StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom());
258 		StrokeLine(BPoint(rect.right, rect.top + 1.0f));
259 	}
260 
261 	rect.InsetBy(-1.0f, -1.0f);
262 
263 	if (enabled)
264 		SetHighColor(darken1);
265 	else
266 		SetHighColor(no_tint);
267 
268 	StrokeLine(rect.LeftBottom(), rect.LeftTop());
269 	StrokeLine(rect.RightTop());
270 
271 	if (enabled)
272 		SetHighColor(lighten2);
273 	else
274 		SetHighColor(lighten1);
275 
276 	StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom());
277 	StrokeLine(BPoint(rect.right, rect.top + 1.0f), rect.RightBottom());
278 
279 	if (Label()) {
280 		font_height fh;
281 		GetFontHeight(&fh);
282 
283 		float y = (float)ceil(fh.ascent + fh.descent + fh.leading) + 2.0f;
284 		float x;
285 
286 		switch (fLabelAlign) {
287 			case B_ALIGN_RIGHT:
288 				x = fDivider - StringWidth(Label()) - 3.0f;
289 				break;
290 
291 			case B_ALIGN_CENTER:
292 				x = fDivider - StringWidth(Label()) / 2.0f;
293 				break;
294 
295 			default:
296 				x = 3.0f;
297 				break;
298 		}
299 
300 		SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
301 					 IsEnabled() ? B_DARKEN_MAX_TINT : B_DISABLED_LABEL_TINT));
302 		DrawString(Label(), BPoint(x, y));
303 	}
304 }
305 
306 
307 void
308 BTextControl::MouseDown(BPoint where)
309 {
310 	if (!fText->IsFocus()) {
311 		fText->MakeFocus(true);
312 		fText->SelectAll();
313 	}
314 }
315 
316 
317 void
318 BTextControl::AttachedToWindow()
319 {
320 	BControl::AttachedToWindow();
321 
322 	bool enabled = IsEnabled();
323 	rgb_color textColor;
324 	rgb_color color = HighColor();
325 	BFont font;
326 
327 	fText->GetFontAndColor(0, &font, &color);
328 
329 	if (enabled)
330 		textColor = color;
331 	else
332 		textColor = tint_color(color, B_LIGHTEN_2_TINT);
333 
334 	fText->SetFontAndColor(&font, B_FONT_ALL, &textColor);
335 
336 	if (enabled) {
337 		color.red = 255;
338 		color.green = 255;
339 		color.blue = 255;
340 	} else
341 		color = tint_color(color, B_LIGHTEN_2_TINT);
342 
343 	fText->SetViewColor(color);
344 	fText->SetLowColor(color);
345 
346 	fText->MakeEditable(enabled);
347 }
348 
349 
350 void
351 BTextControl::MakeFocus(bool state)
352 {
353 	if (state != fText->IsFocus()) {
354 		fText->MakeFocus(state);
355 
356 		if (state)
357 			fText->SelectAll();
358 	}
359 }
360 
361 
362 void
363 BTextControl::SetEnabled(bool state)
364 {
365 	if (IsEnabled() == state)
366 		return;
367 
368 	if (Window()) {
369 		fText->MakeEditable(state);
370 
371 		rgb_color textColor;
372 		rgb_color color = {0, 0, 0, 255};
373 		BFont font;
374 
375 		fText->GetFontAndColor(0, &font, &color);
376 
377 		if (state)
378 			textColor = color;
379 		else
380 			textColor = tint_color(color, B_DISABLED_LABEL_TINT);
381 
382 		fText->SetFontAndColor(&font, B_FONT_ALL, &textColor);
383 
384 		if (state) {
385 			color.red = 255;
386 			color.green = 255;
387 			color.blue = 255;
388 		} else
389 			color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
390 				B_LIGHTEN_2_TINT);
391 
392 		fText->SetViewColor(color);
393 		fText->SetLowColor(color);
394 
395 		fText->Invalidate();
396 		Window()->UpdateIfNeeded();
397 	}
398 
399 	BControl::SetEnabled(state);
400 }
401 
402 
403 void
404 BTextControl::GetPreferredSize(float *width, float *height)
405 {
406 	if (height)
407 		*height = fText->LineHeight(0) + 8.0f;
408 
409 	// TODO: this one I need to find out
410 	if (width)
411 		*width = 4.0f + ceilf(StringWidth(Label())) * 2.0f;
412 }
413 
414 
415 void
416 BTextControl::ResizeToPreferred()
417 {
418 	float width, height;
419 	GetPreferredSize(&width, &height);
420 	BView::ResizeTo(width, height);
421 }
422 
423 
424 void
425 BTextControl::SetFlags(uint32 flags)
426 {
427 	if (!fSkipSetFlags) {
428 		// If the textview is navigable, set it to not navigable if needed
429 		// Else if it is not navigable, set it to navigable if needed
430 		if (fText->Flags() & B_NAVIGABLE) {
431 			if (!(flags & B_NAVIGABLE))
432 				fText->SetFlags(fText->Flags() & ~B_NAVIGABLE);
433 
434 		} else {
435 			if (flags & B_NAVIGABLE)
436 				fText->SetFlags(fText->Flags() | B_NAVIGABLE);
437 		}
438 
439 		// Don't make this one navigable
440 		flags &= ~B_NAVIGABLE;
441 	}
442 
443 	BView::SetFlags(flags);
444 }
445 
446 
447 void
448 BTextControl::MessageReceived(BMessage *msg)
449 {
450 	switch(msg->what) {
451 		case B_SET_PROPERTY:
452 		case B_GET_PROPERTY:
453 			// TODO
454 			break;
455 		default:
456 			BControl::MessageReceived(msg);
457 			break;
458 	}
459 }
460 
461 
462 BHandler *
463 BTextControl::ResolveSpecifier(BMessage *msg, int32 index,
464 										 BMessage *specifier, int32 form,
465 										 const char *property)
466 {
467 	/*
468 	BPropertyInfo propInfo(prop_list);
469 	BHandler *target = NULL;
470 
471 	if (propInfo.FindMatch(message, 0, specifier, what, property) < B_OK)
472 		return BControl::ResolveSpecifier(message, index, specifier, what,
473 			property);
474 	else
475 		return this;
476 	*/
477 	return BControl::ResolveSpecifier(msg, index, specifier, form, property);
478 }
479 
480 
481 status_t
482 BTextControl::GetSupportedSuites(BMessage *data)
483 {
484 	return BControl::GetSupportedSuites(data);
485 }
486 
487 
488 void
489 BTextControl::MouseUp(BPoint pt)
490 {
491 	BControl::MouseUp(pt);
492 }
493 
494 
495 void
496 BTextControl::MouseMoved(BPoint pt, uint32 code, const BMessage *msg)
497 {
498 	BControl::MouseMoved(pt, code, msg);
499 }
500 
501 
502 void
503 BTextControl::DetachedFromWindow()
504 {
505 	BControl::DetachedFromWindow();
506 }
507 
508 
509 void
510 BTextControl::AllAttached()
511 {
512 	BControl::AllAttached();
513 }
514 
515 
516 void
517 BTextControl::AllDetached()
518 {
519 	BControl::AllDetached();
520 }
521 
522 
523 void
524 BTextControl::FrameMoved(BPoint newPosition)
525 {
526 	BControl::FrameMoved(newPosition);
527 }
528 
529 
530 void
531 BTextControl::FrameResized(float newWidth, float newHeight)
532 {
533 	BControl::FrameResized(newWidth, newHeight);
534 }
535 
536 
537 void
538 BTextControl::WindowActivated(bool active)
539 {
540 	if (fText->IsFocus()) {
541 		BRect rect(fText->Frame());
542 		rect.InsetBy(-1.0, -1.0);
543 		Invalidate(rect);
544 	}
545 }
546 
547 
548 status_t
549 BTextControl::Perform(perform_code d, void *arg)
550 {
551 	return BControl::Perform(d, arg);
552 }
553 
554 
555 void BTextControl::_ReservedTextControl1() {}
556 void BTextControl::_ReservedTextControl2() {}
557 void BTextControl::_ReservedTextControl3() {}
558 void BTextControl::_ReservedTextControl4() {}
559 
560 
561 BTextControl &
562 BTextControl::operator=(const BTextControl&)
563 {
564 	return *this;
565 }
566 
567 
568 void
569 BTextControl::CommitValue()
570 {
571 }
572 
573 
574 void
575 BTextControl::InitData(const char *label, const char *initial_text,
576 							BMessage *data)
577 {
578 	BRect bounds(Bounds());
579 
580 	fText = NULL;
581 	fModificationMessage = NULL;
582 	fLabelAlign = B_ALIGN_LEFT;
583 	fDivider = 0.0f;
584 	fPrevWidth = 0;
585 	fPrevHeight = 0;
586 	fSkipSetFlags = false;
587 
588 	int32 flags = 0;
589 
590 	BFont font(be_plain_font);
591 
592 	if (!data || !data->HasString("_fname"))
593 		flags |= B_FONT_FAMILY_AND_STYLE;
594 
595 	if (!data || !data->HasFloat("_fflt"))
596 		flags |= B_FONT_SIZE;
597 
598 	if (flags != 0)
599 		SetFont(&font, flags);
600 
601 	if (label)
602 		fDivider = floorf(bounds.Width() / 2.0f);
603 
604 	if (Flags() & B_NAVIGABLE) {
605 		fSkipSetFlags = true;
606 		SetFlags(Flags() & ~B_NAVIGABLE);
607 		fSkipSetFlags = false;
608 	}
609 
610 	if (data)
611 		fText = static_cast<_BTextInput_ *>(FindView("_input_"));
612 	else {
613 		BRect frame(fDivider, bounds.top,
614 					bounds.right, bounds.bottom);
615 		// we are stroking the frame arround the text view, which
616 		// is 2 pixels wide
617 		frame.InsetBy(2.0, 2.0);
618 
619 		BRect textRect(frame.OffsetToCopy(0.0f, 0.0f));
620 		textRect.InsetBy(2.0, 2.0);
621 
622 		fText = new _BTextInput_(frame, textRect,
623 								 B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP,
624 								 B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE);
625 
626 		AddChild(fText);
627 
628 		SetText(initial_text);
629 		fText->SetAlignment(B_ALIGN_LEFT);
630 		fText->AlignTextRect();
631 	}
632 }
633