xref: /haiku/src/kits/interface/Alert.cpp (revision cbe0a0c436162d78cc3f92a305b64918c839d079)
1 /*
2  * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Axel Dörfler, axeld@pinc-software.de
7  *		Erik Jaesler, erik@cgsoftware.com
8  *		John Scipione, jscipione@gmail.com
9  */
10 
11 
12 //!	BAlert displays a modal alert window.
13 
14 
15 #include <Alert.h>
16 
17 #include <new>
18 
19 #include <stdio.h>
20 
21 #include <Bitmap.h>
22 #include <Button.h>
23 #include <ControlLook.h>
24 #include <FindDirectory.h>
25 #include <IconUtils.h>
26 #include <LayoutBuilder.h>
27 #include <MenuField.h>
28 #include <MessageFilter.h>
29 #include <Path.h>
30 #include <Resources.h>
31 #include <Screen.h>
32 #include <String.h>
33 #include <Window.h>
34 
35 #include <binary_compatibility/Interface.h>
36 
37 
38 //#define DEBUG_ALERT
39 #ifdef DEBUG_ALERT
40 #	define FTRACE(x) fprintf(x)
41 #else
42 #	define FTRACE(x) ;
43 #endif
44 
45 
46 class TAlertView : public BView {
47 public:
48 								TAlertView();
49 								TAlertView(BMessage* archive);
50 								~TAlertView();
51 
52 	static	TAlertView*			Instantiate(BMessage* archive);
53 	virtual	status_t			Archive(BMessage* archive,
54 									bool deep = true) const;
55 
56 	virtual	void				GetPreferredSize(float* _width, float* _height);
57 	virtual	BSize				MaxSize();
58 	virtual	void				Draw(BRect updateRect);
59 
60 			void				SetBitmap(BBitmap* Icon)
61 									{ fIconBitmap = Icon; }
62 			BBitmap*			Bitmap()
63 									{ return fIconBitmap; }
64 
65 private:
66 			BBitmap*			fIconBitmap;
67 };
68 
69 
70 class _BAlertFilter_ : public BMessageFilter {
71 public:
72 								_BAlertFilter_(BAlert* Alert);
73 								~_BAlertFilter_();
74 
75 	virtual	filter_result		Filter(BMessage* msg, BHandler** target);
76 
77 private:
78 			BAlert*				fAlert;
79 };
80 
81 
82 static const unsigned int kAlertButtonMsg = 'ALTB';
83 static const int kSemTimeOut = 50000;
84 
85 static const int kButtonOffsetSpacing = 62;
86 static const int kButtonUsualWidth = 55;
87 static const int kIconStripeWidth = 30;
88 
89 static const int kWindowMinWidth = 310;
90 static const int kWindowOffsetMinWidth = 335;
91 
92 
93 static inline float
94 icon_layout_scale()
95 {
96 	float scale = be_plain_font->Size() / 12;
97 	return max_c(1, scale);
98 }
99 
100 
101 // #pragma mark -
102 
103 
104 BAlert::BAlert()
105 	:
106 	BWindow(BRect(0, 0, 100, 100), "", B_MODAL_WINDOW,
107 		B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS)
108 {
109 	_Init(NULL, NULL, NULL, NULL, B_WIDTH_FROM_WIDEST, B_EVEN_SPACING,
110 		B_INFO_ALERT);
111 }
112 
113 
114 BAlert::BAlert(const char *title, const char *text, const char *button1,
115 		const char *button2, const char *button3, button_width width,
116 		alert_type type)
117 	:
118 	BWindow(BRect(0, 0, 100, 100), title, B_MODAL_WINDOW,
119 		B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS)
120 {
121 	_Init(text, button1, button2, button3, width, B_EVEN_SPACING, type);
122 }
123 
124 
125 BAlert::BAlert(const char *title, const char *text, const char *button1,
126 		const char *button2, const char *button3, button_width width,
127 		button_spacing spacing, alert_type type)
128 	:
129 	BWindow(BRect(0, 0, 100, 100), title, B_MODAL_WINDOW,
130 		B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS)
131 {
132 	_Init(text, button1, button2, button3, width, spacing, type);
133 }
134 
135 
136 BAlert::BAlert(BMessage* data)
137 	:
138 	BWindow(data)
139 {
140 	fInvoker = NULL;
141 	fAlertSem = -1;
142 	fAlertValue = -1;
143 
144 	fTextView = (BTextView*)FindView("_tv_");
145 
146 	// TODO: window loses default button on dearchive!
147 	// TODO: ButtonAt() doesn't work afterwards (also affects shortcuts)
148 
149 	TAlertView* view = (TAlertView*)FindView("_master_");
150 	if (view)
151 		view->SetBitmap(_CreateTypeIcon());
152 
153 	// Get keys
154 	char key;
155 	for (int32 i = 0; i < 3; ++i) {
156 		if (data->FindInt8("_but_key", i, (int8*)&key) == B_OK)
157 			fKeys[i] = key;
158 	}
159 
160 	AddCommonFilter(new(std::nothrow) _BAlertFilter_(this));
161 }
162 
163 
164 BAlert::~BAlert()
165 {
166 	// Probably not necessary, but it makes me feel better.
167 	if (fAlertSem >= B_OK)
168 		delete_sem(fAlertSem);
169 }
170 
171 
172 BArchivable*
173 BAlert::Instantiate(BMessage* data)
174 {
175 	if (!validate_instantiation(data, "BAlert"))
176 		return NULL;
177 
178 	return new(std::nothrow) BAlert(data);
179 }
180 
181 
182 status_t
183 BAlert::Archive(BMessage* data, bool deep) const
184 {
185 	status_t ret = BWindow::Archive(data, deep);
186 
187 	// Stow the text
188 	if (ret == B_OK)
189 		ret = data->AddString("_text", fTextView->Text());
190 
191 	// Stow the alert type
192 	if (ret == B_OK)
193 		ret = data->AddInt32("_atype", fType);
194 
195 	// Stow the button width
196 	if (ret == B_OK)
197 		ret = data->AddInt32("_but_width", fButtonWidth);
198 
199 	// Stow the shortcut keys
200 	if (fKeys[0] || fKeys[1] || fKeys[2]) {
201 		// If we have any to save, we must save something for everyone so it
202 		// doesn't get confusing on the unarchive.
203 		if (ret == B_OK)
204 			ret = data->AddInt8("_but_key", fKeys[0]);
205 		if (ret == B_OK)
206 			ret = data->AddInt8("_but_key", fKeys[1]);
207 		if (ret == B_OK)
208 			ret = data->AddInt8("_but_key", fKeys[2]);
209 	}
210 
211 	return ret;
212 }
213 
214 
215 alert_type
216 BAlert::Type() const
217 {
218 	return (alert_type)fType;
219 }
220 
221 
222 void
223 BAlert::SetType(alert_type type)
224 {
225 	fType = type;
226 }
227 
228 
229 void
230 BAlert::SetText(const char* text)
231 {
232 	TextView()->SetText(text);
233 }
234 
235 
236 void
237 BAlert::SetIcon(BBitmap* bitmap)
238 {
239 	fIconView->SetBitmap(bitmap);
240 }
241 
242 
243 void
244 BAlert::SetButtonSpacing(button_spacing spacing)
245 {
246 	fButtonSpacing = spacing;
247 }
248 
249 
250 void
251 BAlert::SetButtonWidth(button_width width)
252 {
253 	fButtonWidth = width;
254 }
255 
256 
257 void
258 BAlert::SetShortcut(int32 index, char key)
259 {
260 	if (index >= 0 && (size_t)index < fKeys.size())
261 		fKeys[index] = key;
262 }
263 
264 
265 char
266 BAlert::Shortcut(int32 index) const
267 {
268 	if (index >= 0 && (size_t)index < fKeys.size())
269 		return fKeys[index];
270 
271 	return 0;
272 }
273 
274 
275 int32
276 BAlert::Go()
277 {
278 	fAlertSem = create_sem(0, "AlertSem");
279 	if (fAlertSem < 0) {
280 		Quit();
281 		return -1;
282 	}
283 
284 	// Get the originating window, if it exists
285 	BWindow* window = dynamic_cast<BWindow*>(
286 		BLooper::LooperForThread(find_thread(NULL)));
287 
288 	_Prepare();
289 	Show();
290 
291 	if (window != NULL) {
292 		status_t status;
293 		for (;;) {
294 			do {
295 				status = acquire_sem_etc(fAlertSem, 1, B_RELATIVE_TIMEOUT,
296 					kSemTimeOut);
297 				// We've (probably) had our time slice taken away from us
298 			} while (status == B_INTERRUPTED);
299 
300 			if (status == B_BAD_SEM_ID) {
301 				// Semaphore was finally nuked in MessageReceived
302 				break;
303 			}
304 			window->UpdateIfNeeded();
305 		}
306 	} else {
307 		// No window to update, so just hang out until we're done.
308 		while (acquire_sem(fAlertSem) == B_INTERRUPTED) {
309 		}
310 	}
311 
312 	// Have to cache the value since we delete on Quit()
313 	int32 value = fAlertValue;
314 	if (Lock())
315 		Quit();
316 
317 	return value;
318 }
319 
320 
321 status_t
322 BAlert::Go(BInvoker* invoker)
323 {
324 	fInvoker = invoker;
325 	_Prepare();
326 	Show();
327 	return B_OK;
328 }
329 
330 
331 void
332 BAlert::MessageReceived(BMessage* msg)
333 {
334 	if (msg->what != kAlertButtonMsg)
335 		return BWindow::MessageReceived(msg);
336 
337 	int32 which;
338 	if (msg->FindInt32("which", &which) == B_OK) {
339 		if (fAlertSem < 0) {
340 			// Semaphore hasn't been created; we're running asynchronous
341 			if (fInvoker != NULL) {
342 				BMessage* out = fInvoker->Message();
343 				if (out && (out->ReplaceInt32("which", which) == B_OK
344 							|| out->AddInt32("which", which) == B_OK))
345 					fInvoker->Invoke();
346 			}
347 			PostMessage(B_QUIT_REQUESTED);
348 		} else {
349 			// Created semaphore means were running synchronously
350 			fAlertValue = which;
351 
352 			// TextAlertVar does release_sem() below, and then sets the
353 			// member var.  That doesn't make much sense to me, since we
354 			// want to be able to clean up at some point.  Better to just
355 			// nuke the semaphore now; we don't need it any more and this
356 			// lets synchronous Go() continue just as well.
357 			delete_sem(fAlertSem);
358 			fAlertSem = -1;
359 		}
360 	}
361 }
362 
363 
364 void
365 BAlert::FrameResized(float newWidth, float newHeight)
366 {
367 	BWindow::FrameResized(newWidth, newHeight);
368 }
369 
370 
371 void
372 BAlert::AddButton(const char* label, char key)
373 {
374 	if (label == NULL || label[0] == '\0')
375 		return;
376 
377 	BButton* button = _CreateButton(fButtons.size(), label);
378 	fButtons.push_back(button);
379 	fKeys.push_back(key);
380 
381 	SetDefaultButton(button);
382 	fButtonLayout->AddView(button);
383 }
384 
385 
386 int32
387 BAlert::CountButtons() const
388 {
389 	return (int32)fButtons.size();
390 }
391 
392 
393 BButton*
394 BAlert::ButtonAt(int32 index) const
395 {
396 	if (index >= 0 && (size_t)index < fButtons.size())
397 		return fButtons[index];
398 
399 	return NULL;
400 }
401 
402 
403 BTextView*
404 BAlert::TextView() const
405 {
406 	return fTextView;
407 }
408 
409 
410 BHandler*
411 BAlert::ResolveSpecifier(BMessage* msg, int32 index,
412 	BMessage* specifier, int32 form, const char* property)
413 {
414 	return BWindow::ResolveSpecifier(msg, index, specifier, form, property);
415 }
416 
417 
418 status_t
419 BAlert::GetSupportedSuites(BMessage* data)
420 {
421 	return BWindow::GetSupportedSuites(data);
422 }
423 
424 
425 void
426 BAlert::DispatchMessage(BMessage* msg, BHandler* handler)
427 {
428 	BWindow::DispatchMessage(msg, handler);
429 }
430 
431 
432 void
433 BAlert::Quit()
434 {
435 	BWindow::Quit();
436 }
437 
438 
439 bool
440 BAlert::QuitRequested()
441 {
442 	return BWindow::QuitRequested();
443 }
444 
445 
446 //! This method is deprecated, do not use - use BWindow::CenterIn() instead.
447 BPoint
448 BAlert::AlertPosition(float width, float height)
449 {
450 	BPoint result(100, 100);
451 
452 	BWindow* window =
453 		dynamic_cast<BWindow*>(BLooper::LooperForThread(find_thread(NULL)));
454 
455 	BScreen screen(window);
456 	BRect screenFrame(0, 0, 640, 480);
457 	if (screen.IsValid())
458 		screenFrame = screen.Frame();
459 
460 	// Horizontally, we're smack in the middle
461 	result.x = screenFrame.left + (screenFrame.Width() / 2.0) - (width / 2.0);
462 
463 	// This is probably sooo wrong, but it looks right on 1024 x 768
464 	result.y = screenFrame.top + (screenFrame.Height() / 4.0) - ceil(height / 3.0);
465 
466 	return result;
467 }
468 
469 
470 status_t
471 BAlert::Perform(perform_code code, void* _data)
472 {
473 	switch (code) {
474 		case PERFORM_CODE_SET_LAYOUT:
475 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
476 			BAlert::SetLayout(data->layout);
477 			return B_OK;
478 	}
479 
480 	return BWindow::Perform(code, _data);
481 }
482 
483 
484 void BAlert::_ReservedAlert1() {}
485 void BAlert::_ReservedAlert2() {}
486 void BAlert::_ReservedAlert3() {}
487 
488 
489 void
490 BAlert::_Init(const char* text, const char* button0, const char* button1,
491 	const char* button2, button_width buttonWidth, button_spacing spacing,
492 	alert_type type)
493 {
494 	fInvoker = NULL;
495 	fAlertSem = -1;
496 	fAlertValue = -1;
497 
498 	fIconView = new TAlertView();
499 
500 	fTextView = new BTextView("_tv_");
501 	fTextView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
502 	rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR);
503 	fTextView->SetFontAndColor(be_plain_font, B_FONT_ALL, &textColor);
504 	fTextView->MakeEditable(false);
505 	fTextView->MakeSelectable(false);
506 	fTextView->SetWordWrap(true);
507 	fTextView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
508 
509 	fButtonLayout = new BGroupLayout(B_HORIZONTAL, B_USE_HALF_ITEM_SPACING);
510 
511 	SetType(type);
512 	SetButtonWidth(buttonWidth);
513 	SetButtonSpacing(spacing);
514 	SetText(text);
515 
516 	BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0)
517 		.Add(fIconView)
518 		.AddGroup(B_VERTICAL, B_USE_HALF_ITEM_SPACING)
519 			.SetInsets(B_USE_HALF_ITEM_INSETS)
520 			.Add(fTextView)
521 			.AddGroup(B_HORIZONTAL, 0)
522 				.AddGlue()
523 				.Add(fButtonLayout);
524 
525 	AddButton(button0);
526 	AddButton(button1);
527 	AddButton(button2);
528 
529 	AddCommonFilter(new(std::nothrow) _BAlertFilter_(this));
530 }
531 
532 
533 BBitmap*
534 BAlert::_CreateTypeIcon()
535 {
536 	if (Type() == B_EMPTY_ALERT)
537 		return NULL;
538 
539 	// The icons are in the app_server resources
540 	BBitmap* icon = NULL;
541 	BPath path;
542 	status_t status = find_directory(B_BEOS_SERVERS_DIRECTORY, &path);
543 	if (status != B_OK) {
544 		FTRACE((stderr, "BAlert::_CreateTypeIcon() - find_directory "
545 			"failed: %s\n", strerror(status)));
546 		return NULL;
547 	}
548 
549 	path.Append("app_server");
550 	BFile file;
551 	status = file.SetTo(path.Path(), B_READ_ONLY);
552 	if (status != B_OK) {
553 		FTRACE((stderr, "BAlert::_CreateTypeIcon() - BFile init failed: %s\n",
554 			strerror(status)));
555 		return NULL;
556 	}
557 
558 	BResources resources;
559 	status = resources.SetTo(&file);
560 	if (status != B_OK) {
561 		FTRACE((stderr, "BAlert::_CreateTypeIcon() - BResources init "
562 			"failed: %s\n", strerror(status)));
563 		return NULL;
564 	}
565 
566 	// Which icon are we trying to load?
567 	const char* iconName;
568 	switch (fType) {
569 		case B_INFO_ALERT:
570 			iconName = "info";
571 			break;
572 		case B_IDEA_ALERT:
573 			iconName = "idea";
574 			break;
575 		case B_WARNING_ALERT:
576 			iconName = "warn";
577 			break;
578 		case B_STOP_ALERT:
579 			iconName = "stop";
580 			break;
581 
582 		default:
583 			// Alert type is either invalid or B_EMPTY_ALERT;
584 			// either way, we're not going to load an icon
585 			return NULL;
586 	}
587 
588 	int32 iconSize = (int32)(32 * icon_layout_scale());
589 	// Allocate the icon bitmap
590 	icon = new(std::nothrow) BBitmap(BRect(0, 0, iconSize - 1, iconSize - 1),
591 		0, B_RGBA32);
592 	if (icon == NULL || icon->InitCheck() < B_OK) {
593 		FTRACE((stderr, "BAlert::_CreateTypeIcon() - No memory for bitmap\n"));
594 		delete icon;
595 		return NULL;
596 	}
597 
598 	// Load the raw icon data
599 	size_t size = 0;
600 	const uint8* rawIcon;
601 
602 	// Try to load vector icon
603 	rawIcon = (const uint8*)resources.LoadResource(B_VECTOR_ICON_TYPE,
604 		iconName, &size);
605 	if (rawIcon != NULL
606 		&& BIconUtils::GetVectorIcon(rawIcon, size, icon) == B_OK) {
607 		return icon;
608 	}
609 
610 	// Fall back to bitmap icon
611 	rawIcon = (const uint8*)resources.LoadResource(B_LARGE_ICON_TYPE,
612 		iconName, &size);
613 	if (rawIcon == NULL) {
614 		FTRACE((stderr, "BAlert::_CreateTypeIcon() - Icon resource not found\n"));
615 		delete icon;
616 		return NULL;
617 	}
618 
619 	// Handle color space conversion
620 	if (icon->ColorSpace() != B_CMAP8) {
621 		BIconUtils::ConvertFromCMAP8(rawIcon, iconSize, iconSize,
622 			iconSize, icon);
623 	}
624 
625 	return icon;
626 }
627 
628 
629 BButton*
630 BAlert::_CreateButton(int32 which, const char* label)
631 {
632 	BMessage* message = new BMessage(kAlertButtonMsg);
633 	if (message == NULL)
634 		return NULL;
635 
636 	message->AddInt32("which", which);
637 
638 	char name[32];
639 	snprintf(name, sizeof(name), "_b%" B_PRId32 "_", which);
640 
641 	return new(std::nothrow) BButton(name, label, message);
642 }
643 
644 
645 /*!	Tweaks the layout according to the configuration.
646 */
647 void
648 BAlert::_Prepare()
649 {
650 	// Must have at least one button
651 	if (CountButtons() == 0)
652 		debugger("BAlerts must have at least one button.");
653 
654 	float fontFactor = be_plain_font->Size() / 11.0f;
655 
656 	if (fIconView->Bitmap() == NULL)
657 		fIconView->SetBitmap(_CreateTypeIcon());
658 
659 	if (fButtonWidth == B_WIDTH_AS_USUAL) {
660 		float usualWidth = kButtonUsualWidth * fontFactor;
661 
662 		for (int32 index = 0; index < CountButtons(); index++) {
663 			BButton* button = ButtonAt(index);
664 			if (button->MinSize().width < usualWidth)
665 				button->SetExplicitSize(BSize(usualWidth, B_SIZE_UNSET));
666 		}
667 	} else if (fButtonWidth == B_WIDTH_FROM_WIDEST) {
668 		// Get width of widest label
669 		float maxWidth = 0;
670 		for (int32 index = 0; index < CountButtons(); index++) {
671 			BButton* button = ButtonAt(index);
672 			float width;
673 			button->GetPreferredSize(&width, NULL);
674 
675 			if (width > maxWidth)
676 				maxWidth = width;
677 		}
678 		for (int32 index = 0; index < CountButtons(); index++) {
679 			BButton* button = ButtonAt(index);
680 			button->SetExplicitSize(BSize(maxWidth, B_SIZE_UNSET));
681 		}
682 	}
683 
684 	if (fButtonSpacing == B_OFFSET_SPACING && CountButtons() > 1) {
685 		// Insert some strut
686 		fButtonLayout->AddItem(1, BSpaceLayoutItem::CreateHorizontalStrut(
687 			kButtonOffsetSpacing * fontFactor));
688 	}
689 
690 	// Position the alert so that it is centered vertically but offset a bit
691 	// horizontally in the parent window's frame or, if unavailable, the
692 	// screen frame.
693 	float minWindowWidth = (fButtonSpacing == B_OFFSET_SPACING
694 		? kWindowOffsetMinWidth : kWindowMinWidth) * fontFactor;
695 	GetLayout()->SetExplicitMinSize(BSize(minWindowWidth, B_SIZE_UNSET));
696 
697 	ResizeToPreferred();
698 
699 	// Return early if we've already been moved...
700 	if (Frame().left != 0 && Frame().right != 0)
701 		return;
702 
703 	// otherwise center ourselves on-top of parent window/screen
704 	BWindow* parent = dynamic_cast<BWindow*>(BLooper::LooperForThread(
705 		find_thread(NULL)));
706 	const BRect frame = parent != NULL ? parent->Frame()
707 		: BScreen(this).Frame();
708 
709 	MoveTo(static_cast<BWindow*>(this)->AlertPosition(frame));
710 		// Hidden by BAlert::AlertPosition()
711 }
712 
713 
714 //	#pragma mark - TAlertView
715 
716 
717 TAlertView::TAlertView()
718 	:
719 	BView("TAlertView", B_WILL_DRAW),
720 	fIconBitmap(NULL)
721 {
722 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
723 }
724 
725 
726 TAlertView::TAlertView(BMessage* archive)
727 	:
728 	BView(archive),
729 	fIconBitmap(NULL)
730 {
731 }
732 
733 
734 TAlertView::~TAlertView()
735 {
736 	delete fIconBitmap;
737 }
738 
739 
740 TAlertView*
741 TAlertView::Instantiate(BMessage* archive)
742 {
743 	if (!validate_instantiation(archive, "TAlertView"))
744 		return NULL;
745 
746 	return new(std::nothrow) TAlertView(archive);
747 }
748 
749 
750 status_t
751 TAlertView::Archive(BMessage* archive, bool deep) const
752 {
753 	return BView::Archive(archive, deep);
754 }
755 
756 
757 void
758 TAlertView::GetPreferredSize(float* _width, float* _height)
759 {
760 	float scale = icon_layout_scale();
761 
762 	if (_width != NULL) {
763 		if (fIconBitmap != NULL)
764 			*_width = 18 * scale + fIconBitmap->Bounds().Width();
765 		else
766 			*_width = 0;
767 	}
768 	if (_height != NULL) {
769 		if (fIconBitmap != NULL)
770 			*_height = 6 * scale + fIconBitmap->Bounds().Height();
771 		else
772 			*_height = 0;
773 	}
774 }
775 
776 
777 BSize
778 TAlertView::MaxSize()
779 {
780 	return BSize(MinSize().width, B_SIZE_UNLIMITED);
781 }
782 
783 
784 void
785 TAlertView::Draw(BRect updateRect)
786 {
787 	if (fIconBitmap == NULL)
788 		return;
789 
790 	// Here's the fun stuff
791 	BRect stripeRect = Bounds();
792 	float iconLayoutScale = icon_layout_scale();
793 	stripeRect.right = kIconStripeWidth * iconLayoutScale;
794 	SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT));
795 	FillRect(stripeRect);
796 
797 	SetDrawingMode(B_OP_ALPHA);
798 	SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
799 	DrawBitmapAsync(fIconBitmap, BPoint(18 * iconLayoutScale,
800 		6 * iconLayoutScale));
801 }
802 
803 
804 //	#pragma mark - _BAlertFilter_
805 
806 
807 _BAlertFilter_::_BAlertFilter_(BAlert* alert)
808 	: BMessageFilter(B_KEY_DOWN),
809 	fAlert(alert)
810 {
811 }
812 
813 
814 _BAlertFilter_::~_BAlertFilter_()
815 {
816 }
817 
818 
819 filter_result
820 _BAlertFilter_::Filter(BMessage* msg, BHandler** target)
821 {
822 	if (msg->what == B_KEY_DOWN) {
823 		char byte;
824 		if (msg->FindInt8("byte", (int8*)&byte) == B_OK) {
825 			for (int i = 0; i < fAlert->CountButtons(); ++i) {
826 				if (byte == fAlert->Shortcut(i) && fAlert->ButtonAt(i)) {
827 					char space = ' ';
828 					fAlert->ButtonAt(i)->KeyDown(&space, 1);
829 
830 					return B_SKIP_MESSAGE;
831 				}
832 			}
833 		}
834 	}
835 
836 	return B_DISPATCH_MESSAGE;
837 }
838