xref: /haiku/src/apps/mediaplayer/MainApp.cpp (revision 220d04022750f40f8bac8f01fa551211e28d04f2)
1 /*
2  * MainApp.cpp - Media Player for the Haiku Operating System
3  *
4  * Copyright (C) 2006 Marcus Overhagen <marcus@overhagen.de>
5  * Copyright (C) 2008 Stephan Aßmus <superstippi@gmx.de> (MIT Ok)
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * version 2 as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  *
20  */
21 
22 
23 #include "MainApp.h"
24 
25 #include <Alert.h>
26 #include <Autolock.h>
27 #include <Catalog.h>
28 #include <Entry.h>
29 #include <FilePanel.h>
30 #include <Locale.h>
31 #include <MediaDefs.h>
32 #include <MediaRoster.h>
33 #include <MimeType.h>
34 #include <Path.h>
35 #include <Resources.h>
36 #include <Roster.h>
37 
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <unistd.h>
41 
42 #include "EventQueue.h"
43 #include "Playlist.h"
44 #include "Settings.h"
45 #include "SettingsWindow.h"
46 
47 
48 #undef B_TRANSLATION_CONTEXT
49 #define B_TRANSLATION_CONTEXT "MediaPlayer-Main"
50 
51 
52 static const char* kCurrentPlaylistFilename = "MediaPlayer Current Playlist";
53 
54 const char* kAppSig = "application/x-vnd.Haiku-MediaPlayer";
55 
56 MainApp* gMainApp;
57 
58 static const char* kMediaServerSig = B_MEDIA_SERVER_SIGNATURE;
59 static const char* kMediaServerAddOnSig = "application/x-vnd.Be.addon-host";
60 
61 
62 MainApp::MainApp()
63 	:
64 	BApplication(kAppSig),
65 	fPlayerCount(0),
66 	fSettingsWindow(NULL),
67 
68 	fOpenFilePanel(NULL),
69 	fSaveFilePanel(NULL),
70 	fLastFilePanelFolder(),
71 
72 	fMediaServerRunning(false),
73 	fMediaAddOnServerRunning(false),
74 
75 	fAudioWindowFrameSaved(false),
76 	fLastSavedAudioWindowCreationTime(0)
77 {
78 	fLastFilePanelFolder = Settings::Default()->FilePanelFolder();
79 
80 	// Now tell the application roster, that we're interested
81 	// in getting notifications of apps being launched or quit.
82 	// In this way we are going to detect a media_server restart.
83 	be_roster->StartWatching(BMessenger(this, this),
84 		B_REQUEST_LAUNCHED | B_REQUEST_QUIT);
85 	// we will keep track of the status of media_server
86 	// and media_addon_server
87 	fMediaServerRunning = be_roster->IsRunning(kMediaServerSig);
88 	fMediaAddOnServerRunning = be_roster->IsRunning(kMediaServerAddOnSig);
89 
90 	if (!fMediaServerRunning || !fMediaAddOnServerRunning) {
91 		BAlert* alert = new BAlert("start_media_server",
92 			B_TRANSLATE("It appears the media server is not running.\n"
93 			"Would you like to start it ?"), B_TRANSLATE("Quit"),
94 			B_TRANSLATE("Start media server"), NULL,
95 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
96 		alert->SetShortcut(0, B_ESCAPE);
97 
98 		if (alert->Go() == 0) {
99 			PostMessage(B_QUIT_REQUESTED);
100 			return;
101 		}
102 
103 		launch_media_server();
104 
105 		fMediaServerRunning = be_roster->IsRunning(kMediaServerSig);
106 		fMediaAddOnServerRunning = be_roster->IsRunning(kMediaServerAddOnSig);
107 	}
108 }
109 
110 
111 MainApp::~MainApp()
112 {
113 	delete fOpenFilePanel;
114 	delete fSaveFilePanel;
115 }
116 
117 
118 bool
119 MainApp::QuitRequested()
120 {
121 	// Make sure we store the current playlist, if applicable.
122 	for (int32 i = 0; BWindow* window = WindowAt(i); i++) {
123 		MainWin* playerWindow = dynamic_cast<MainWin*>(window);
124 		if (playerWindow == NULL)
125 			continue;
126 
127 		BAutolock _(playerWindow);
128 
129 		BMessage quitMessage;
130 		playerWindow->GetQuitMessage(&quitMessage);
131 
132 		// Store the playlist if there is one. If the user has multiple
133 		// instances playing audio at the this time, the first instance wins.
134 		BMessage playlistArchive;
135 		if (quitMessage.FindMessage("playlist", &playlistArchive) == B_OK) {
136 			_StoreCurrentPlaylist(&playlistArchive);
137 			break;
138 		}
139 	}
140 
141 	// Note: This needs to be done here, SettingsWindow::QuitRequested()
142 	// returns "false" always. (Standard BApplication quit procedure will
143 	// hang otherwise.)
144 	if (fSettingsWindow && fSettingsWindow->Lock())
145 		fSettingsWindow->Quit();
146 	fSettingsWindow = NULL;
147 
148 	// store the current file panel ref in the global settings
149 	Settings::Default()->SetFilePanelFolder(fLastFilePanelFolder);
150 
151 	return BApplication::QuitRequested();
152 }
153 
154 
155 MainWin*
156 MainApp::NewWindow(BMessage* message)
157 {
158 	BAutolock _(this);
159 	fPlayerCount++;
160 	return new(std::nothrow) MainWin(fPlayerCount == 1, message);
161 }
162 
163 
164 int32
165 MainApp::PlayerCount() const
166 {
167 	BAutolock _(const_cast<MainApp*>(this));
168 	return fPlayerCount;
169 }
170 
171 
172 // #pragma mark -
173 
174 
175 void
176 MainApp::ReadyToRun()
177 {
178 	// make sure we have at least one window open
179 	if (fPlayerCount == 0) {
180 		MainWin* window = NewWindow();
181 		if (window == NULL) {
182 			PostMessage(B_QUIT_REQUESTED);
183 			return;
184 		}
185 		BMessage lastPlaylistArchive;
186 		if (_RestoreCurrentPlaylist(&lastPlaylistArchive) == B_OK) {
187 			lastPlaylistArchive.what = M_OPEN_PREVIOUS_PLAYLIST;
188 			window->PostMessage(&lastPlaylistArchive);
189 		} else
190 			window->Show();
191 	}
192 
193 	// setup the settings window now, we need to have it
194 	fSettingsWindow = new SettingsWindow(BRect(150, 150, 450, 520));
195 	fSettingsWindow->Hide();
196 	fSettingsWindow->Show();
197 
198 	_InstallPlaylistMimeType();
199 }
200 
201 
202 void
203 MainApp::RefsReceived(BMessage* message)
204 {
205 	// The user dropped a file (or files) on this app's icon,
206 	// or double clicked a file that's handled by this app.
207 	// Command line arguments are also redirected to here by
208 	// ArgvReceived() but without MIME type check.
209 
210 	// If multiple refs are received in short succession we
211 	// combine them into a single window/playlist. Tracker
212 	// will send multiple messages when opening a multi-
213 	// selection for example and we don't want to spawn large
214 	// numbers of windows when someone just tries to open an
215 	// album. We use half a second time and prolong it for
216 	// each new ref received.
217 	static bigtime_t sLastRefsReceived = 0;
218 	static MainWin* sLastRefsWindow = NULL;
219 
220 	if (system_time() - sLastRefsReceived < 500000) {
221 		// Find the last opened window
222 		for (int32 i = CountWindows() - 1; i >= 0; i--) {
223 			MainWin* playerWindow = dynamic_cast<MainWin*>(WindowAt(i));
224 			if (playerWindow == NULL)
225 				continue;
226 
227 			if (playerWindow != sLastRefsWindow) {
228 				// The window has changed since the last refs
229 				sLastRefsReceived = 0;
230 				sLastRefsWindow = NULL;
231 				break;
232 			}
233 
234 			message->AddBool("append to playlist", true);
235 			playerWindow->PostMessage(message);
236 			sLastRefsReceived = system_time();
237 			return;
238 		}
239 	}
240 
241 	sLastRefsWindow = NewWindow(message);
242 	sLastRefsReceived = system_time();
243 }
244 
245 
246 void
247 MainApp::ArgvReceived(int32 argc, char** argv)
248 {
249 	char cwd[B_PATH_NAME_LENGTH];
250 	getcwd(cwd, sizeof(cwd));
251 
252 	BMessage message(B_REFS_RECEIVED);
253 
254 	for (int i = 1; i < argc; i++) {
255 		BPath path;
256 		if (argv[i][0] != '/')
257 			path.SetTo(cwd, argv[i]);
258 		else
259 			path.SetTo(argv[i]);
260 		BEntry entry(path.Path(), true);
261 		if (!entry.Exists() || !entry.IsFile())
262 			continue;
263 
264 		entry_ref ref;
265 		if (entry.GetRef(&ref) == B_OK)
266 			message.AddRef("refs", &ref);
267 	}
268 
269 	if (message.HasRef("refs"))
270 		RefsReceived(&message);
271 }
272 
273 
274 void
275 MainApp::MessageReceived(BMessage* message)
276 {
277 	switch (message->what) {
278 		case M_NEW_PLAYER:
279 		{
280 			MainWin* window = NewWindow();
281 			if (window != NULL)
282 				window->Show();
283 			break;
284 		}
285 		case M_PLAYER_QUIT:
286 		{
287 			// store the window settings of this instance
288 			MainWin* window = NULL;
289 			bool audioOnly = false;
290 			BRect windowFrame;
291 			bigtime_t creationTime;
292 			if (message->FindPointer("instance", (void**)&window) == B_OK
293 				&& message->FindBool("audio only", &audioOnly) == B_OK
294 				&& message->FindRect("window frame", &windowFrame) == B_OK
295 				&& message->FindInt64("creation time", &creationTime) == B_OK) {
296 				if (audioOnly && (!fAudioWindowFrameSaved
297 						|| creationTime < fLastSavedAudioWindowCreationTime)) {
298 					fAudioWindowFrameSaved = true;
299 					fLastSavedAudioWindowCreationTime = creationTime;
300 
301 					Settings::Default()->SetAudioPlayerWindowFrame(windowFrame);
302 				}
303 			}
304 
305 			// Store the playlist if there is one. Since the app is doing
306 			// this, it is "atomic". If the user has multiple instances
307 			// playing audio at the same time, the last instance which is
308 			// quit wins.
309 			BMessage playlistArchive;
310 			if (message->FindMessage("playlist", &playlistArchive) == B_OK)
311 				_StoreCurrentPlaylist(&playlistArchive);
312 
313 			// quit if this was the last player window
314 			fPlayerCount--;
315 			if (fPlayerCount == 0)
316 				PostMessage(B_QUIT_REQUESTED);
317 			break;
318 		}
319 
320 		case B_SOME_APP_LAUNCHED:
321 		case B_SOME_APP_QUIT:
322 		{
323 			const char* mimeSig;
324 			if (message->FindString("be:signature", &mimeSig) < B_OK)
325 				break;
326 
327 			bool isMediaServer = strcmp(mimeSig, kMediaServerSig) == 0;
328 			bool isAddonServer = strcmp(mimeSig, kMediaServerAddOnSig) == 0;
329 			if (!isMediaServer && !isAddonServer)
330 				break;
331 
332 			bool running = (message->what == B_SOME_APP_LAUNCHED);
333 			if (isMediaServer)
334 				fMediaServerRunning = running;
335 			if (isAddonServer)
336 				fMediaAddOnServerRunning = running;
337 
338 			if (!fMediaServerRunning && !fMediaAddOnServerRunning) {
339 				fprintf(stderr, "media server has quit.\n");
340 				// trigger closing of media nodes
341 				BMessage broadcast(M_MEDIA_SERVER_QUIT);
342 				_BroadcastMessage(broadcast);
343 			} else if (fMediaServerRunning && fMediaAddOnServerRunning) {
344 				fprintf(stderr, "media server has launched.\n");
345 				// HACK!
346 				// quit our now invalid instance of the media roster
347 				// so that before new nodes are created,
348 				// we get a new roster (it is a normal looper)
349 				// TODO: This functionality could become part of
350 				// BMediaRoster. It could detect the start/quit of
351 				// the servers like it is done here, and either quit
352 				// itself, or re-establish the connection, and send some
353 				// notification to the app... something along those lines.
354 				BMediaRoster* roster = BMediaRoster::CurrentRoster();
355 				if (roster) {
356 					roster->Lock();
357 					roster->Quit();
358 				}
359 				// give the servers some time to init...
360 				snooze(3000000);
361 				// trigger re-init of media nodes
362 				BMessage broadcast(M_MEDIA_SERVER_STARTED);
363 				_BroadcastMessage(broadcast);
364 			}
365 			break;
366 		}
367 		case M_SETTINGS:
368 			_ShowSettingsWindow();
369 			break;
370 
371 		case M_SHOW_OPEN_PANEL:
372 			_ShowOpenFilePanel(message);
373 			break;
374 		case M_SHOW_SAVE_PANEL:
375 			_ShowSaveFilePanel(message);
376 			break;
377 
378 		case M_OPEN_PANEL_RESULT:
379 			_HandleOpenPanelResult(message);
380 			break;
381 		case M_SAVE_PANEL_RESULT:
382 			_HandleSavePanelResult(message);
383 			break;
384 		case B_CANCEL:
385 		{
386 			// The user canceled a file panel, but store at least the current
387 			// file panel folder.
388 			uint32 oldWhat;
389 			if (message->FindInt32("old_what", (int32*)&oldWhat) != B_OK)
390 				break;
391 			if (oldWhat == M_OPEN_PANEL_RESULT && fOpenFilePanel != NULL)
392 				fOpenFilePanel->GetPanelDirectory(&fLastFilePanelFolder);
393 			else if (oldWhat == M_SAVE_PANEL_RESULT && fSaveFilePanel != NULL)
394 				fSaveFilePanel->GetPanelDirectory(&fLastFilePanelFolder);
395 			break;
396 		}
397 
398 		default:
399 			BApplication::MessageReceived(message);
400 			break;
401 	}
402 }
403 
404 
405 // #pragma mark -
406 
407 
408 void
409 MainApp::_BroadcastMessage(const BMessage& _message)
410 {
411 	for (int32 i = 0; BWindow* window = WindowAt(i); i++) {
412 		BMessage message(_message);
413 		window->PostMessage(&message);
414 	}
415 }
416 
417 
418 void
419 MainApp::_ShowSettingsWindow()
420 {
421 	BAutolock lock(fSettingsWindow);
422 	if (!lock.IsLocked())
423 		return;
424 
425 	// If the window is already showing, don't jerk the workspaces around,
426 	// just pull it to the current one.
427 	uint32 workspace = 1UL << (uint32)current_workspace();
428 	uint32 windowWorkspaces = fSettingsWindow->Workspaces();
429 	if ((windowWorkspaces & workspace) == 0) {
430 		// window in a different workspace, reopen in current
431 		fSettingsWindow->SetWorkspaces(workspace);
432 	}
433 
434 	if (fSettingsWindow->IsHidden())
435 		fSettingsWindow->Show();
436 	else
437 		fSettingsWindow->Activate();
438 }
439 
440 
441 // #pragma mark - file panels
442 
443 
444 void
445 MainApp::_ShowOpenFilePanel(const BMessage* message)
446 {
447 	if (fOpenFilePanel == NULL) {
448 		BMessenger target(this);
449 		fOpenFilePanel = new BFilePanel(B_OPEN_PANEL, &target);
450 	}
451 
452 	_ShowFilePanel(fOpenFilePanel, M_OPEN_PANEL_RESULT, message,
453 		B_TRANSLATE("Open"), B_TRANSLATE("Open"));
454 }
455 
456 
457 void
458 MainApp::_ShowSaveFilePanel(const BMessage* message)
459 {
460 	if (fSaveFilePanel == NULL) {
461 		BMessenger target(this);
462 		fSaveFilePanel = new BFilePanel(B_SAVE_PANEL, &target);
463 	}
464 
465 	_ShowFilePanel(fSaveFilePanel, M_SAVE_PANEL_RESULT, message,
466 		B_TRANSLATE("Save"), B_TRANSLATE("Save"));
467 }
468 
469 
470 void
471 MainApp::_ShowFilePanel(BFilePanel* panel, uint32 command,
472 	const BMessage* message, const char* defaultTitle,
473 	const char* defaultLabel)
474 {
475 //	printf("_ShowFilePanel()\n");
476 //	message->PrintToStream();
477 
478 	BMessage panelMessage(command);
479 
480 	if (message != NULL) {
481 		BMessage targetMessage;
482 		if (message->FindMessage("message", &targetMessage) == B_OK)
483 			panelMessage.AddMessage("message", &targetMessage);
484 
485 		BMessenger target;
486 		if (message->FindMessenger("target", &target) == B_OK)
487 			panelMessage.AddMessenger("target", target);
488 
489 		const char* panelTitle;
490 		if (message->FindString("title", &panelTitle) != B_OK)
491 			panelTitle = defaultTitle;
492 		{
493 			BString finalPanelTitle = "MediaPlayer: ";
494 			finalPanelTitle << panelTitle;
495 			BAutolock lock(panel->Window());
496 			panel->Window()->SetTitle(finalPanelTitle.String());
497 		}
498 		const char* buttonLabel;
499 		if (message->FindString("label", &buttonLabel) != B_OK)
500 			buttonLabel = defaultLabel;
501 		panel->SetButtonLabel(B_DEFAULT_BUTTON, buttonLabel);
502 	}
503 
504 //	panelMessage.PrintToStream();
505 	panel->SetMessage(&panelMessage);
506 
507 	if (fLastFilePanelFolder != entry_ref()) {
508 		panel->SetPanelDirectory(&fLastFilePanelFolder);
509 	}
510 
511 	panel->Show();
512 }
513 
514 
515 void
516 MainApp::_HandleOpenPanelResult(const BMessage* message)
517 {
518 	_HandleFilePanelResult(fOpenFilePanel, message);
519 }
520 
521 
522 void
523 MainApp::_HandleSavePanelResult(const BMessage* message)
524 {
525 	_HandleFilePanelResult(fSaveFilePanel, message);
526 }
527 
528 
529 void
530 MainApp::_HandleFilePanelResult(BFilePanel* panel, const BMessage* message)
531 {
532 //	printf("_HandleFilePanelResult()\n");
533 //	message->PrintToStream();
534 
535 	panel->GetPanelDirectory(&fLastFilePanelFolder);
536 
537 	BMessage targetMessage;
538 	if (message->FindMessage("message", &targetMessage) != B_OK)
539 		targetMessage.what = message->what;
540 
541 	BMessenger target;
542 	if (message->FindMessenger("target", &target) != B_OK) {
543 		if (targetMessage.what == M_OPEN_PANEL_RESULT
544 			|| targetMessage.what == M_SAVE_PANEL_RESULT) {
545 			// prevent endless message cycle
546 			return;
547 		}
548 		// send result message to ourselves
549 		target = BMessenger(this);
550 	}
551 
552 	// copy the important contents of the message
553 	// save panel
554 	entry_ref directory;
555 	if (message->FindRef("directory", &directory) == B_OK)
556 		targetMessage.AddRef("directory", &directory);
557 	const char* name;
558 	if (message->FindString("name", &name) == B_OK)
559 		targetMessage.AddString("name", name);
560 	// open panel
561 	entry_ref ref;
562 	for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++)
563 		targetMessage.AddRef("refs", &ref);
564 
565 	target.SendMessage(&targetMessage);
566 }
567 
568 
569 void
570 MainApp::_StoreCurrentPlaylist(const BMessage* message) const
571 {
572 	BPath path;
573 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK
574 		|| path.Append(kCurrentPlaylistFilename) != B_OK) {
575 		return;
576 	}
577 
578 	BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
579 	if (file.InitCheck() != B_OK)
580 		return;
581 
582 	message->Flatten(&file);
583 }
584 
585 
586 status_t
587 MainApp::_RestoreCurrentPlaylist(BMessage* message) const
588 {
589 	BPath path;
590 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK
591 		|| path.Append(kCurrentPlaylistFilename) != B_OK) {
592 		return B_ERROR;
593 	}
594 
595 	BFile file(path.Path(), B_READ_ONLY);
596 	if (file.InitCheck() != B_OK)
597 		return B_ERROR;
598 
599 	return message->Unflatten(&file);
600 }
601 
602 
603 void
604 MainApp::_InstallPlaylistMimeType()
605 {
606 	// install mime type of documents
607 	BMimeType mime(kBinaryPlaylistMimeString);
608 	status_t ret = mime.InitCheck();
609 	if (ret != B_OK) {
610 		fprintf(stderr, "Could not init native document mime type (%s): %s.\n",
611 			kBinaryPlaylistMimeString, strerror(ret));
612 		return;
613 	}
614 
615 	if (mime.IsInstalled() && !(modifiers() & B_SHIFT_KEY)) {
616 		// mime is already installed, and the user is not
617 		// pressing the shift key to force a re-install
618 		return;
619 	}
620 
621 	ret = mime.Install();
622 	if (ret != B_OK && ret != B_FILE_EXISTS) {
623 		fprintf(stderr, "Could not install native document mime type (%s): %s.\n",
624 			kBinaryPlaylistMimeString, strerror(ret));
625 		return;
626 	}
627 	// set preferred app
628 	ret = mime.SetPreferredApp(kAppSig);
629 	if (ret != B_OK) {
630 		fprintf(stderr, "Could not set native document preferred app: %s\n",
631 			strerror(ret));
632 	}
633 
634 	// set descriptions
635 	ret = mime.SetShortDescription("MediaPlayer playlist");
636 	if (ret != B_OK) {
637 		fprintf(stderr, "Could not set short description of mime type: %s\n",
638 			strerror(ret));
639 	}
640 	ret = mime.SetLongDescription("MediaPlayer binary playlist file");
641 	if (ret != B_OK) {
642 		fprintf(stderr, "Could not set long description of mime type: %s\n",
643 			strerror(ret));
644 	}
645 
646 	// set extensions
647 	BMessage message('extn');
648 	message.AddString("extensions", "playlist");
649 	ret = mime.SetFileExtensions(&message);
650 	if (ret != B_OK) {
651 		fprintf(stderr, "Could not set extensions of mime type: %s\n",
652 			strerror(ret));
653 	}
654 
655 	// set sniffer rule
656 	char snifferRule[32];
657 	uint32 bigEndianMagic = B_HOST_TO_BENDIAN_INT32(kPlaylistMagicBytes);
658 	sprintf(snifferRule, "0.9 ('%4s')", (const char*)&bigEndianMagic);
659 	ret = mime.SetSnifferRule(snifferRule);
660 	if (ret != B_OK) {
661 		BString parseError;
662 		BMimeType::CheckSnifferRule(snifferRule, &parseError);
663 		fprintf(stderr, "Could not set sniffer rule of mime type: %s\n",
664 			parseError.String());
665 	}
666 
667 	// set playlist icon
668 	BResources* resources = AppResources();
669 		// does not need to be freed (belongs to BApplication base)
670 	if (resources != NULL) {
671 		size_t size;
672 		const void* iconData = resources->LoadResource('VICN', "PlaylistIcon",
673 			&size);
674 		if (iconData != NULL && size > 0) {
675 			if (mime.SetIcon(reinterpret_cast<const uint8*>(iconData), size)
676 				!= B_OK) {
677 				fprintf(stderr, "Could not set vector icon of mime type.\n");
678 			}
679 		} else {
680 			fprintf(stderr, "Could not find icon in app resources "
681 				"(data: %p, size: %ld).\n", iconData, size);
682 		}
683 	} else
684 		fprintf(stderr, "Could not find app resources.\n");
685 }
686 
687 
688 // #pragma mark - main
689 
690 
691 int
692 main()
693 {
694 	EventQueue::CreateDefault();
695 
696 	srand(system_time());
697 
698 	gMainApp = new MainApp;
699 	gMainApp->Run();
700 	delete gMainApp;
701 
702 	EventQueue::DeleteDefault();
703 
704 	return 0;
705 }
706