xref: /haiku/src/kits/interface/Button.cpp (revision fef6144999c2fa611f59ee6ffe6dd7999501385c)
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 		if (!fDrawAsDefault) {
279 			fDrawAsDefault = true;
280 
281 			ResizeBy(6.0f, 6.0f);
282 			MoveBy(-3.0f, -3.0f);
283 		}
284 
285 		if (window && oldDefault != this)
286 			window->SetDefaultButton(this);
287 	} else {
288 		if (!fDrawAsDefault)
289 			return;
290 
291 		fDrawAsDefault = false;
292 
293 		ResizeBy(-6.0f, -6.0f);
294 		MoveBy(3.0f, 3.0f);
295 
296 		if (window && oldDefault == this)
297 			window->SetDefaultButton(NULL);
298 	}
299 }
300 
301 
302 void
303 BButton::SetLabel(const char *string)
304 {
305 	BControl::SetLabel(string);
306 }
307 
308 
309 bool
310 BButton::IsDefault() const
311 {
312 	return fDrawAsDefault;
313 }
314 
315 
316 void
317 BButton::MessageReceived(BMessage *message)
318 {
319 	BControl::MessageReceived(message);
320 }
321 
322 
323 void
324 BButton::WindowActivated(bool active)
325 {
326 	BControl::WindowActivated(active);
327 }
328 
329 
330 void
331 BButton::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
332 {
333 	if (!IsTracking())
334 		return;
335 
336 	bool inside = Bounds().Contains(point);
337 
338 	if ((Value() == B_CONTROL_ON) != inside)
339 		SetValue(inside ? B_CONTROL_ON : B_CONTROL_OFF);
340 }
341 
342 
343 void
344 BButton::MouseUp(BPoint point)
345 {
346 	if (!IsTracking())
347 		return;
348 
349 	if (Bounds().Contains(point))
350 		Invoke();
351 
352 	SetTracking(false);
353 }
354 
355 
356 void
357 BButton::DetachedFromWindow()
358 {
359 	BControl::DetachedFromWindow();
360 }
361 
362 
363 void
364 BButton::SetValue(int32 value)
365 {
366 	if (value != Value()) {
367 		BControl::SetValue(value);
368 		Invalidate();
369 	}
370 }
371 
372 
373 void
374 BButton::GetPreferredSize(float *width, float *height)
375 {
376 	font_height fh;
377 	GetFontHeight(&fh);
378 
379 	*height = 12.0f + (float)ceil(fh.ascent + fh.descent);
380 	*width = 20.0f + (float)ceil(StringWidth(Label()));
381 
382 	if (*width < 75.0f)
383 		*width = 75.0f;
384 
385 	if (fDrawAsDefault) {
386 		*width += 6.0f;
387 		*height += 6.0f;
388 	}
389 }
390 
391 
392 void
393 BButton::ResizeToPreferred()
394 {
395 	 BControl::ResizeToPreferred();
396 }
397 
398 
399 status_t
400 BButton::Invoke(BMessage *message)
401 {
402 	Sync();
403 	snooze(50000);
404 
405 	status_t err = BControl::Invoke(message);
406 
407 	SetValue(B_CONTROL_OFF);
408 
409 	return err;
410 }
411 
412 
413 void
414 BButton::FrameMoved(BPoint newLocation)
415 {
416 	BControl::FrameMoved(newLocation);
417 }
418 
419 
420 void
421 BButton::FrameResized(float width, float height)
422 {
423 	BControl::FrameResized(width, height);
424 }
425 
426 
427 void
428 BButton::MakeFocus(bool focused)
429 {
430 	BControl::MakeFocus(focused);
431 }
432 
433 
434 void
435 BButton::AllAttached()
436 {
437 	BControl::AllAttached();
438 }
439 
440 
441 void
442 BButton::AllDetached()
443 {
444 	BControl::AllDetached();
445 }
446 
447 
448 BHandler *
449 BButton::ResolveSpecifier(BMessage *message, int32 index,
450 									BMessage *specifier, int32 what,
451 									const char *property)
452 {
453 	return BControl::ResolveSpecifier(message, index, specifier, what, property);
454 }
455 
456 
457 status_t
458 BButton::GetSupportedSuites(BMessage *message)
459 {
460 	return BControl::GetSupportedSuites(message);
461 }
462 
463 
464 status_t
465 BButton::Perform(perform_code d, void *arg)
466 {
467 	return BControl::Perform(d, arg);
468 }
469 
470 
471 void BButton::_ReservedButton1() {}
472 void BButton::_ReservedButton2() {}
473 void BButton::_ReservedButton3() {}
474 
475 
476 BButton &
477 BButton::operator=(const BButton &)
478 {
479 	return *this;
480 }
481 
482 
483 BRect
484 BButton::DrawDefault(BRect bounds, bool enabled)
485 {
486 	rgb_color no_tint = ui_color(B_PANEL_BACKGROUND_COLOR),
487 	lighten1 = tint_color(no_tint, B_LIGHTEN_1_TINT),
488 	darken1 = tint_color(no_tint, B_DARKEN_1_TINT);
489 
490 	rgb_color borderColor;
491 	if (enabled)
492 		borderColor = tint_color(no_tint, B_DARKEN_4_TINT);
493 	else
494 		borderColor = darken1;
495 
496 	// Dark border
497 	BeginLineArray(4);
498 	AddLine(BPoint(bounds.left, bounds.bottom - 1.0f),
499 		BPoint(bounds.left, bounds.top + 1.0f), borderColor);
500 	AddLine(BPoint(bounds.left + 1.0f, bounds.top),
501 		BPoint(bounds.right - 1.0f, bounds.top), borderColor);
502 	AddLine(BPoint(bounds.right, bounds.top + 1.0f),
503 		BPoint(bounds.right, bounds.bottom - 1.0f), borderColor);
504 	AddLine(BPoint(bounds.left + 1.0f, bounds.bottom),
505 		BPoint(bounds.right - 1.0f, bounds.bottom), borderColor);
506 	EndLineArray();
507 
508 	if (enabled) {
509 		// Bevel
510 		bounds.InsetBy(1.0f, 1.0f);
511 		SetHighColor(darken1);
512 		StrokeRect(bounds);
513 	}
514 
515 	bounds.InsetBy(1.0f, 1.0f);
516 
517 	// Filling
518 	float inset = enabled? 2.0f : 3.0f;
519 	SetHighColor(lighten1);
520 
521 	FillRect(BRect(bounds.left, bounds.top,
522 		bounds.right, bounds.top+inset-1.0f));
523 	FillRect(BRect(bounds.left, bounds.bottom-inset+1.0f,
524 		bounds.right, bounds.bottom));
525 	FillRect(BRect(bounds.left, bounds.top+inset-1.0f,
526 		bounds.left+inset-1.0f, bounds.bottom-inset+1.0f));
527 	FillRect(BRect(bounds.right-inset+1.0f, bounds.top+inset-1.0f,
528 		bounds.right, bounds.bottom-inset+1.0f));
529 
530 	bounds.InsetBy(inset,inset);
531 
532 	return bounds;
533 }
534 
535 
536 status_t
537 BButton::Execute()
538 {
539 	if (!IsEnabled())
540 		return B_ERROR;
541 
542 	SetValue(B_CONTROL_ON);
543 	return Invoke();
544 }
545 
546 
547 void
548 BButton::DrawFocusLine(float x, float y, float width, bool visible)
549 {
550 	if (visible)
551 		SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
552 	else
553 		SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
554 			B_LIGHTEN_1_TINT));
555 	// Blue Line
556 	StrokeLine(BPoint(x, y), BPoint(x + width, y));
557 
558 	if (visible)
559 		SetHighColor(255, 255, 255);
560 	// White Line
561 	StrokeLine(BPoint(x, y + 1.0f), BPoint(x + width, y + 1.0f));
562 }
563