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