xref: /haiku/src/kits/interface/Button.cpp (revision 0b2dbe7d46ee888392907c60131b7f7652314175)
1 /*
2  *	Copyright (c) 2001-2005, Haiku.
3  *
4  *	Authors:
5  *				Marc Flerackers (mflerackers@androme.be)
6  *				Mike Wilber
7  *				Stefano Ceccherini (burton666@libero.it)
8  *				Ivan Tonizza
9  */
10 
11 #include <Button.h>
12 #include <Font.h>
13 #include <String.h>
14 #include <Window.h>
15 
16 BButton::BButton(BRect frame, const char *name, const char *label, BMessage *message,
17 				  uint32 resizingMode, uint32 flags)
18 	:	BControl(frame, name, label, message, resizingMode, flags |= B_WILL_DRAW),
19 		fDrawAsDefault(false)
20 {
21 	// Resize to minimum height if needed
22 	font_height fh;
23 	GetFontHeight(&fh);
24 	float minHeight = 12.0f + (float)ceil(fh.ascent + fh.descent);
25 	if (Bounds().Height() < minHeight)
26 		ResizeTo(Bounds().Width(), minHeight);
27 }
28 
29 
30 BButton::~BButton()
31 {
32 }
33 
34 
35 BButton::BButton(BMessage *archive)
36 	:	BControl (archive)
37 {
38 	if (archive->FindBool("_default", &fDrawAsDefault) != B_OK)
39 		fDrawAsDefault = false;
40 }
41 
42 
43 BArchivable *
44 BButton::Instantiate(BMessage *archive)
45 {
46 	if (validate_instantiation(archive, "BButton"))
47 		return new BButton(archive);
48 	else
49 		return NULL;
50 }
51 
52 
53 status_t
54 BButton::Archive(BMessage* archive, bool deep) const
55 {
56 	status_t err = BControl::Archive(archive, deep);
57 
58 	if (err != B_OK)
59 		return err;
60 
61 	if (IsDefault())
62 		err = archive->AddBool("_default", true);
63 
64 	return err;
65 }
66 
67 
68 void
69 BButton::Draw(BRect updateRect)
70 {
71 	font_height fh;
72 	GetFontHeight(&fh);
73 
74 	const BRect bounds = Bounds();
75 	BRect rect = bounds;
76 
77 	const bool enabled = IsEnabled();
78 	const bool pushed = Value() == B_CONTROL_ON;
79 
80 	// Default indicator
81 	if (IsDefault())
82 		rect = DrawDefault(rect,enabled);
83 	else
84 		rect.InsetBy(1.0f,1.0f);
85 
86 	BRect fillArea = rect;
87 	fillArea.InsetBy(3.0f,3.0f);
88 
89 	BString text = Label();
90 
91 #if 1
92 	// Label truncation
93 	BFont font;
94 	GetFont(&font);
95 	font.TruncateString(&text, B_TRUNCATE_END, fillArea.Width());
96 #endif
97 
98 	// Label position
99 	const float stringWidth = StringWidth(text.String());
100 	const float x = (bounds.right - stringWidth) / 2.0f;
101 	const float labelY = bounds.top
102 		+ ((bounds.Height() - fh.ascent - fh.descent) / 2.0f)
103 		+ fh.ascent +  1.0f;
104 	const float focusLineY = labelY + fh.descent;
105 
106 	/* speed trick:
107 	   if the focus changes but the button is not pressed then we can
108 	   redraw only the focus line,
109 	   if the focus changes and the button is pressed invert the internal rect
110 	   this block takes care of all the focus changes
111 	*/
112 	if (IsFocusChanging()) {
113 		if (pushed) {
114 			rect.InsetBy(2.0,2.0);
115 			InvertRect(rect);
116 		} else
117 			DrawFocusLine(x, focusLineY, stringWidth, IsFocus() && Window()->IsActive());
118 
119 		return;
120 	}
121 
122 	// Colors
123 	const rgb_color panelBgColor = ui_color(B_PANEL_BACKGROUND_COLOR);
124 	const rgb_color buttonBgColor=tint_color(panelBgColor, B_LIGHTEN_1_TINT);
125 	const rgb_color maxLightColor=tint_color(panelBgColor, B_LIGHTEN_MAX_TINT);
126 	const rgb_color maxShadowColor=tint_color(panelBgColor, B_DARKEN_MAX_TINT);
127 	const rgb_color darkBorderColor = tint_color(panelBgColor,
128 		enabled ? B_DARKEN_4_TINT : B_DARKEN_2_TINT);
129 	const rgb_color firstBevelColor = enabled ? tint_color(panelBgColor, B_DARKEN_2_TINT)
130 		: panelBgColor;
131 	const rgb_color cornerColor = IsDefault() ? firstBevelColor : panelBgColor;
132 
133 	// Fill the button area
134 	SetHighColor(buttonBgColor);
135 	FillRect(fillArea);
136 
137 	// external border
138 	SetHighColor(darkBorderColor);
139 	StrokeRect(rect);
140 
141 	BeginLineArray(14);
142 
143 	// Corners
144 	AddLine(rect.LeftTop(), rect.LeftTop(), cornerColor);
145 	AddLine(rect.LeftBottom(), rect.LeftBottom(), cornerColor);
146 	AddLine(rect.RightTop(), rect.RightTop(), cornerColor);
147 	AddLine(rect.RightBottom(), rect.RightBottom(), cornerColor);
148 
149 	rect.InsetBy(1.0f,1.0f);
150 
151 	// Shadow
152 	AddLine(rect.LeftBottom(), rect.RightBottom(), firstBevelColor);
153 	AddLine(rect.RightBottom(), rect.RightTop(), firstBevelColor);
154 	// Light
155 	AddLine(rect.LeftTop(), rect.LeftBottom(),buttonBgColor);
156 	AddLine(rect.LeftTop(), rect.RightTop(), buttonBgColor);
157 
158 	rect.InsetBy(1.0f, 1.0f);
159 
160 	// Shadow
161 	AddLine(rect.LeftBottom(), rect.RightBottom(), panelBgColor);
162 	AddLine(rect.RightBottom(), rect.RightTop(), panelBgColor);
163 	// Light
164 	AddLine(rect.LeftTop(), rect.LeftBottom(),maxLightColor);
165 	AddLine(rect.LeftTop(), rect.RightTop(), maxLightColor);
166 
167 	rect.InsetBy(1.0f,1.0f);
168 
169 	// Light
170 	AddLine(rect.LeftTop(), rect.LeftBottom(),maxLightColor);
171 	AddLine(rect.LeftTop(), rect.RightTop(), maxLightColor);
172 
173 	EndLineArray();
174 
175 	// Invert if clicked
176 	if (enabled && pushed) {
177 		rect.InsetBy(-2.0f,-2.0f);
178 		InvertRect(rect);
179 	}
180 
181 	// Label color
182 	if (enabled) {
183 		if (pushed) {
184 			SetHighColor(maxLightColor);
185 			SetLowColor(maxShadowColor);
186 		} else {
187 			SetHighColor(maxShadowColor);
188 			SetLowColor(tint_color(panelBgColor, B_LIGHTEN_2_TINT));
189 		}
190 	} else {
191 		SetHighColor(tint_color(panelBgColor, B_DISABLED_LABEL_TINT));
192 		SetLowColor(tint_color(panelBgColor, B_LIGHTEN_2_TINT));
193 	}
194 
195 	// Draw the label
196 	DrawString(text.String(), BPoint(x, labelY));
197 
198 	// Focus line
199 	if (enabled && IsFocus() && Window()->IsActive() && !pushed)
200 		DrawFocusLine(x,focusLineY,stringWidth,true);
201 
202 }
203 
204 
205 void
206 BButton::MouseDown(BPoint point)
207 {
208 	if (!IsEnabled())
209 		return;
210 
211 	SetValue(B_CONTROL_ON);
212 
213 	if (Window()->Flags() & B_ASYNCHRONOUS_CONTROLS) {
214  		SetTracking(true);
215  		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
216 
217  	} else {
218 		BRect bounds = Bounds();
219 		uint32 buttons;
220 
221 		do {
222 			Window()->UpdateIfNeeded();
223 
224 			snooze(40000);
225 
226 			GetMouse(&point, &buttons, true);
227 
228  			bool inside = bounds.Contains(point);
229 
230 			if ((Value() == B_CONTROL_ON) != inside)
231 				SetValue(inside ? B_CONTROL_ON : B_CONTROL_OFF);
232 		} while (buttons != 0);
233 
234 		if (Value() == B_CONTROL_ON)
235 			Invoke();
236 	}
237 }
238 
239 
240 void
241 BButton::AttachedToWindow()
242 {
243 	BControl::AttachedToWindow();
244 
245 	if (IsDefault())
246 		Window()->SetDefaultButton(this);
247 }
248 
249 
250 void
251 BButton::KeyDown(const char *bytes, int32 numBytes)
252 {
253 	if (*bytes == B_ENTER || *bytes == B_SPACE) {
254 		if (!IsEnabled())
255 			return;
256 
257 		SetValue(B_CONTROL_ON);
258 		Invoke();
259 
260 	} else
261 		BControl::KeyDown(bytes, numBytes);
262 }
263 
264 
265 void
266 BButton::MakeDefault(bool flag)
267 {
268 	BButton *oldDefault = NULL;
269 	BWindow *window = Window();
270 
271 	if (window)
272 		oldDefault = window->DefaultButton();
273 
274 	if (flag) {
275 		if (fDrawAsDefault && oldDefault == this)
276 			return;
277 
278 		fDrawAsDefault = true;
279 
280 		ResizeBy(6.0f, 6.0f);
281 		MoveBy(-3.0f, -3.0f);
282 
283 		if (window && oldDefault != this)
284 			window->SetDefaultButton(this);
285 	} else {
286 		if (!fDrawAsDefault)
287 			return;
288 
289 		fDrawAsDefault = false;
290 
291 		ResizeBy(-6.0f, -6.0f);
292 		MoveBy(3.0f, 3.0f);
293 
294 		if (window && oldDefault == this)
295 			window->SetDefaultButton(NULL);
296 	}
297 }
298 
299 
300 void
301 BButton::SetLabel(const char *string)
302 {
303 	BControl::SetLabel(string);
304 }
305 
306 
307 bool
308 BButton::IsDefault() const
309 {
310 	return fDrawAsDefault;
311 }
312 
313 
314 void
315 BButton::MessageReceived(BMessage *message)
316 {
317 	BControl::MessageReceived(message);
318 }
319 
320 
321 void
322 BButton::WindowActivated(bool active)
323 {
324 	BControl::WindowActivated(active);
325 }
326 
327 
328 void
329 BButton::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
330 {
331 	if (!IsTracking())
332 		return;
333 
334 	bool inside = Bounds().Contains(point);
335 
336 	if ((Value() == B_CONTROL_ON) != inside)
337 		SetValue(inside ? B_CONTROL_ON : B_CONTROL_OFF);
338 }
339 
340 
341 void
342 BButton::MouseUp(BPoint point)
343 {
344 	if (!IsTracking())
345 		return;
346 
347 	if (Bounds().Contains(point))
348 		Invoke();
349 
350 	SetTracking(false);
351 }
352 
353 
354 void
355 BButton::DetachedFromWindow()
356 {
357 	BControl::DetachedFromWindow();
358 }
359 
360 
361 void
362 BButton::SetValue(int32 value)
363 {
364 	if (value != Value()) {
365 		BControl::SetValue(value);
366 		Invalidate();
367 	}
368 }
369 
370 
371 void
372 BButton::GetPreferredSize(float *width, float *height)
373 {
374 	font_height fh;
375 	GetFontHeight(&fh);
376 
377 	*height = 12.0f + (float)ceil(fh.ascent + fh.descent);
378 	*width = 20.0f + (float)ceil(StringWidth(Label()));
379 
380 	if (*width < 75.0f)
381 		*width = 75.0f;
382 
383 	if (fDrawAsDefault) {
384 		*width += 6.0f;
385 		*height += 6.0f;
386 	}
387 }
388 
389 
390 void
391 BButton::ResizeToPreferred()
392 {
393 	 BControl::ResizeToPreferred();
394 }
395 
396 
397 status_t
398 BButton::Invoke(BMessage *message)
399 {
400 	Sync();
401 	snooze(50000);
402 
403 	status_t err = BControl::Invoke(message);
404 
405 	SetValue(B_CONTROL_OFF);
406 
407 	return err;
408 }
409 
410 
411 void
412 BButton::FrameMoved(BPoint newLocation)
413 {
414 	BControl::FrameMoved(newLocation);
415 }
416 
417 
418 void
419 BButton::FrameResized(float width, float height)
420 {
421 	BControl::FrameResized(width, height);
422 }
423 
424 
425 void
426 BButton::MakeFocus(bool focused)
427 {
428 	BControl::MakeFocus(focused);
429 }
430 
431 
432 void
433 BButton::AllAttached()
434 {
435 	BControl::AllAttached();
436 }
437 
438 
439 void
440 BButton::AllDetached()
441 {
442 	BControl::AllDetached();
443 }
444 
445 
446 BHandler *
447 BButton::ResolveSpecifier(BMessage *message, int32 index,
448 									BMessage *specifier, int32 what,
449 									const char *property)
450 {
451 	return BControl::ResolveSpecifier(message, index, specifier, what, property);
452 }
453 
454 
455 status_t
456 BButton::GetSupportedSuites(BMessage *message)
457 {
458 	return BControl::GetSupportedSuites(message);
459 }
460 
461 
462 status_t
463 BButton::Perform(perform_code d, void *arg)
464 {
465 	return BControl::Perform(d, arg);
466 }
467 
468 
469 void BButton::_ReservedButton1() {}
470 void BButton::_ReservedButton2() {}
471 void BButton::_ReservedButton3() {}
472 
473 
474 BButton &
475 BButton::operator=(const BButton &)
476 {
477 	return *this;
478 }
479 
480 
481 BRect
482 BButton::DrawDefault(BRect bounds, bool enabled)
483 {
484 	rgb_color no_tint = ui_color(B_PANEL_BACKGROUND_COLOR),
485 	lighten1 = tint_color(no_tint, B_LIGHTEN_1_TINT),
486 	darken1 = tint_color(no_tint, B_DARKEN_1_TINT);
487 
488 	rgb_color borderColor;
489 	if (enabled)
490 		borderColor = tint_color(no_tint, B_DARKEN_4_TINT);
491 	else
492 		borderColor = darken1;
493 
494 	// Dark border
495 	BeginLineArray(4);
496 	AddLine(BPoint(bounds.left, bounds.bottom - 1.0f),
497 		BPoint(bounds.left, bounds.top + 1.0f), borderColor);
498 	AddLine(BPoint(bounds.left + 1.0f, bounds.top),
499 		BPoint(bounds.right - 1.0f, bounds.top), borderColor);
500 	AddLine(BPoint(bounds.right, bounds.top + 1.0f),
501 		BPoint(bounds.right, bounds.bottom - 1.0f), borderColor);
502 	AddLine(BPoint(bounds.left + 1.0f, bounds.bottom),
503 		BPoint(bounds.right - 1.0f, bounds.bottom), borderColor);
504 	EndLineArray();
505 
506 	if (enabled) {
507 		// Bevel
508 		bounds.InsetBy(1.0f, 1.0f);
509 		SetHighColor(darken1);
510 		StrokeRect(bounds);
511 	}
512 
513 	bounds.InsetBy(1.0f, 1.0f);
514 
515 	// Filling
516 	float inset = enabled? 2.0f : 3.0f;
517 	SetHighColor(lighten1);
518 
519 	FillRect(BRect(bounds.left, bounds.top,
520 		bounds.right, bounds.top+inset-1.0f));
521 	FillRect(BRect(bounds.left, bounds.bottom-inset+1.0f,
522 		bounds.right, bounds.bottom));
523 	FillRect(BRect(bounds.left, bounds.top+inset-1.0f,
524 		bounds.left+inset-1.0f, bounds.bottom-inset+1.0f));
525 	FillRect(BRect(bounds.right-inset+1.0f, bounds.top+inset-1.0f,
526 		bounds.right, bounds.bottom-inset+1.0f));
527 
528 	bounds.InsetBy(inset,inset);
529 
530 	return bounds;
531 }
532 
533 
534 status_t
535 BButton::Execute()
536 {
537 	if (!IsEnabled())
538 		return B_ERROR;
539 
540 	SetValue(B_CONTROL_ON);
541 	return Invoke();
542 }
543 
544 
545 void
546 BButton::DrawFocusLine(float x, float y, float width, bool visible)
547 {
548 	if (visible)
549 		SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
550 	else
551 		SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
552 			B_LIGHTEN_1_TINT));
553 	// Blue Line
554 	StrokeLine(BPoint(x, y), BPoint(x + width, y));
555 
556 	if (visible)
557 		SetHighColor(255, 255, 255);
558 	// White Line
559 	StrokeLine(BPoint(x, y + 1.0f), BPoint(x + width, y + 1.0f));
560 }
561