xref: /haiku/src/apps/haikudepot/ui/MainWindow.cpp (revision a72f3582be00f2151800fa7da036d7adc14e3272)
1 /*
2  * Copyright 2015, Axel Dörfler, <axeld@pinc-software.de>.
3  * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
4  * Copyright 2013, Rene Gollent, rene@gollent.com.
5  * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
6  * Copyright 2016-2019, Andrew Lindesay <apl@lindesay.co.nz>.
7  * Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
8  * All rights reserved. Distributed under the terms of the MIT License.
9  */
10 
11 
12 #include "MainWindow.h"
13 
14 #include <map>
15 #include <vector>
16 
17 #include <stdio.h>
18 #include <Alert.h>
19 #include <Autolock.h>
20 #include <Application.h>
21 #include <Button.h>
22 #include <Catalog.h>
23 #include <CardLayout.h>
24 #include <LayoutBuilder.h>
25 #include <MenuBar.h>
26 #include <MenuItem.h>
27 #include <Messenger.h>
28 #include <Roster.h>
29 #include <Screen.h>
30 #include <ScrollView.h>
31 #include <StringList.h>
32 #include <StringView.h>
33 #include <TabView.h>
34 
35 #include "AppUtils.h"
36 #include "AutoDeleter.h"
37 #include "AutoLocker.h"
38 #include "DecisionProvider.h"
39 #include "FeaturedPackagesView.h"
40 #include "FilterView.h"
41 #include "Logger.h"
42 #include "PackageInfoView.h"
43 #include "PackageListView.h"
44 #include "PackageManager.h"
45 #include "ProcessCoordinator.h"
46 #include "ProcessCoordinatorFactory.h"
47 #include "RatePackageWindow.h"
48 #include "support.h"
49 #include "ScreenshotWindow.h"
50 #include "UserLoginWindow.h"
51 #include "UserUsageConditionsWindow.h"
52 #include "WorkStatusView.h"
53 
54 
55 #undef B_TRANSLATION_CONTEXT
56 #define B_TRANSLATION_CONTEXT "MainWindow"
57 
58 
59 enum {
60 	MSG_BULK_LOAD_DONE						= 'mmwd',
61 	MSG_REFRESH_REPOS						= 'mrrp',
62 	MSG_MANAGE_REPOS						= 'mmrp',
63 	MSG_SOFTWARE_UPDATER					= 'mswu',
64 	MSG_LOG_IN								= 'lgin',
65 	MSG_LOG_OUT								= 'lgot',
66 	MSG_AUTHORIZATION_CHANGED				= 'athc',
67 	MSG_CATEGORIES_LIST_CHANGED				= 'clic',
68 	MSG_PACKAGE_CHANGED						= 'pchd',
69 	MSG_WORK_STATUS_CHANGE					= 'wsch',
70 	MSG_WORK_STATUS_CLEAR					= 'wscl',
71 	MSG_VIEW_LATEST_USER_USAGE_CONDITIONS	= 'vluc',
72 
73 	MSG_SHOW_FEATURED_PACKAGES				= 'sofp',
74 	MSG_SHOW_AVAILABLE_PACKAGES				= 'savl',
75 	MSG_SHOW_INSTALLED_PACKAGES				= 'sins',
76 	MSG_SHOW_SOURCE_PACKAGES				= 'ssrc',
77 	MSG_SHOW_DEVELOP_PACKAGES				= 'sdvl'
78 };
79 
80 
81 using namespace BPackageKit;
82 using namespace BPackageKit::BManager::BPrivate;
83 
84 
85 typedef std::map<BString, PackageInfoRef> PackageInfoMap;
86 
87 
88 struct RefreshWorkerParameters {
89 	MainWindow* window;
90 	bool forceRefresh;
91 
92 	RefreshWorkerParameters(MainWindow* window, bool forceRefresh)
93 		:
94 		window(window),
95 		forceRefresh(forceRefresh)
96 	{
97 	}
98 };
99 
100 
101 class MainWindowModelListener : public ModelListener {
102 public:
103 	MainWindowModelListener(const BMessenger& messenger)
104 		:
105 		fMessenger(messenger)
106 	{
107 	}
108 
109 	virtual void AuthorizationChanged()
110 	{
111 		if (fMessenger.IsValid())
112 			fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED);
113 	}
114 
115 	virtual void CategoryListChanged()
116 	{
117 		if (fMessenger.IsValid())
118 			fMessenger.SendMessage(MSG_CATEGORIES_LIST_CHANGED);
119 	}
120 
121 private:
122 	BMessenger	fMessenger;
123 };
124 
125 
126 MainWindow::MainWindow(const BMessage& settings)
127 	:
128 	BWindow(BRect(50, 50, 650, 550), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
129 		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
130 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
131 	fScreenshotWindow(NULL),
132 	fUserMenu(NULL),
133 	fLogInItem(NULL),
134 	fLogOutItem(NULL),
135 	fModelListener(new MainWindowModelListener(BMessenger(this)), true),
136 	fBulkLoadProcessCoordinator(NULL),
137 	fSinglePackageMode(false)
138 {
139 	BMenuBar* menuBar = new BMenuBar("Main Menu");
140 	_BuildMenu(menuBar);
141 
142 	BMenuBar* userMenuBar = new BMenuBar("User Menu");
143 	_BuildUserMenu(userMenuBar);
144 	set_small_font(userMenuBar);
145 	userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET,
146 		menuBar->MaxSize().height));
147 
148 	fFilterView = new FilterView();
149 	fFeaturedPackagesView = new FeaturedPackagesView();
150 	fPackageListView = new PackageListView(fModel.Lock());
151 	fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
152 
153 	fSplitView = new BSplitView(B_VERTICAL, 5.0f);
154 
155 	fWorkStatusView = new WorkStatusView("work status");
156 	fPackageListView->AttachWorkStatusView(fWorkStatusView);
157 
158 	fListTabs = new TabView(BMessenger(this),
159 		BMessage(MSG_SHOW_FEATURED_PACKAGES), "list tabs");
160 	fListTabs->AddTab(fFeaturedPackagesView);
161 	fListTabs->AddTab(fPackageListView);
162 
163 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f)
164 		.AddGroup(B_HORIZONTAL, 0.0f)
165 			.Add(menuBar, 1.0f)
166 			.Add(userMenuBar, 0.0f)
167 		.End()
168 		.Add(fFilterView)
169 		.AddSplit(fSplitView)
170 			.AddGroup(B_VERTICAL)
171 				.Add(fListTabs)
172 				.SetInsets(
173 					B_USE_DEFAULT_SPACING, 0.0f,
174 					B_USE_DEFAULT_SPACING, 0.0f)
175 			.End()
176 			.Add(fPackageInfoView)
177 		.End()
178 		.Add(fWorkStatusView)
179 	;
180 
181 	fSplitView->SetCollapsible(0, false);
182 	fSplitView->SetCollapsible(1, false);
183 
184 	fModel.AddListener(fModelListener);
185 
186 	// Restore settings
187 	BMessage columnSettings;
188 	if (settings.FindMessage("column settings", &columnSettings) == B_OK)
189 		fPackageListView->LoadState(&columnSettings);
190 
191 	bool showOption;
192 	if (settings.FindBool("show featured packages", &showOption) == B_OK)
193 		fModel.SetShowFeaturedPackages(showOption);
194 	if (settings.FindBool("show available packages", &showOption) == B_OK)
195 		fModel.SetShowAvailablePackages(showOption);
196 	if (settings.FindBool("show installed packages", &showOption) == B_OK)
197 		fModel.SetShowInstalledPackages(showOption);
198 	if (settings.FindBool("show develop packages", &showOption) == B_OK)
199 		fModel.SetShowDevelopPackages(showOption);
200 	if (settings.FindBool("show source packages", &showOption) == B_OK)
201 		fModel.SetShowSourcePackages(showOption);
202 
203 	if (fModel.ShowFeaturedPackages())
204 		fListTabs->Select(0);
205 	else
206 		fListTabs->Select(1);
207 
208 	_RestoreUserName(settings);
209 	_RestoreWindowFrame(settings);
210 
211 	atomic_set(&fPackagesToShowListID, 0);
212 
213 	// start worker threads
214 	BPackageRoster().StartWatching(this,
215 		B_WATCH_PACKAGE_INSTALLATION_LOCATIONS);
216 
217 	_StartBulkLoad();
218 
219 	_InitWorkerThreads();
220 }
221 
222 
223 MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package)
224 	:
225 	BWindow(BRect(50, 50, 650, 350), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
226 		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
227 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
228 	fWorkStatusView(NULL),
229 	fScreenshotWindow(NULL),
230 	fUserMenu(NULL),
231 	fLogInItem(NULL),
232 	fLogOutItem(NULL),
233 	fModelListener(new MainWindowModelListener(BMessenger(this)), true),
234 	fBulkLoadProcessCoordinator(NULL),
235 	fSinglePackageMode(true)
236 {
237 	fFilterView = new FilterView();
238 	fPackageListView = new PackageListView(fModel.Lock());
239 	fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
240 
241 	BLayoutBuilder::Group<>(this, B_VERTICAL)
242 		.Add(fPackageInfoView)
243 		.SetInsets(0, B_USE_WINDOW_INSETS, 0, 0)
244 	;
245 
246 	fModel.AddListener(fModelListener);
247 
248 	// Restore settings
249 	_RestoreUserName(settings);
250 	_RestoreWindowFrame(settings);
251 
252 	fPackageInfoView->SetPackage(package);
253 
254 	_InitWorkerThreads();
255 }
256 
257 
258 MainWindow::~MainWindow()
259 {
260 	BPackageRoster().StopWatching(this);
261 
262 	delete_sem(fPendingActionsSem);
263 	if (fPendingActionsWorker >= 0)
264 		wait_for_thread(fPendingActionsWorker, NULL);
265 
266 	delete_sem(fPackageToPopulateSem);
267 	if (fPopulatePackageWorker >= 0)
268 		wait_for_thread(fPopulatePackageWorker, NULL);
269 
270 	delete_sem(fNewPackagesToShowSem);
271 	delete_sem(fShowPackagesAcknowledgeSem);
272 	if (fShowPackagesWorker >= 0)
273 		wait_for_thread(fShowPackagesWorker, NULL);
274 
275 	if (fScreenshotWindow != NULL && fScreenshotWindow->Lock())
276 		fScreenshotWindow->Quit();
277 }
278 
279 
280 bool
281 MainWindow::QuitRequested()
282 {
283 	BMessage settings;
284 	StoreSettings(settings);
285 
286 	BMessage message(MSG_MAIN_WINDOW_CLOSED);
287 	message.AddMessage(KEY_WINDOW_SETTINGS, &settings);
288 
289 	be_app->PostMessage(&message);
290 
291 	_StopBulkLoad();
292 
293 	return true;
294 }
295 
296 
297 void
298 MainWindow::MessageReceived(BMessage* message)
299 {
300 	switch (message->what) {
301 		case MSG_BULK_LOAD_DONE:
302 			_BulkLoadCompleteReceived();
303 			break;
304 		case B_SIMPLE_DATA:
305 		case B_REFS_RECEIVED:
306 			// TODO: ?
307 			break;
308 
309 		case B_PACKAGE_UPDATE:
310 			// TODO: We should do a more selective update depending on the
311 			// "event", "location", and "change count" fields!
312 			_StartBulkLoad(false);
313 			break;
314 
315 		case MSG_REFRESH_REPOS:
316 			_StartBulkLoad(true);
317 			break;
318 
319 		case MSG_WORK_STATUS_CHANGE:
320 			_HandleWorkStatusChangeMessageReceived(message);
321 			break;
322 
323 		case MSG_MANAGE_REPOS:
324 			be_roster->Launch("application/x-vnd.Haiku-Repositories");
325 			break;
326 
327 		case MSG_SOFTWARE_UPDATER:
328 			be_roster->Launch("application/x-vnd.haiku-softwareupdater");
329 			break;
330 
331 		case MSG_LOG_IN:
332 			_OpenLoginWindow(BMessage());
333 			break;
334 
335 		case MSG_LOG_OUT:
336 			fModel.SetUsername("");
337 			break;
338 
339 		case MSG_VIEW_LATEST_USER_USAGE_CONDITIONS:
340 			_ViewLatestUserUsageConditions();
341 			break;
342 
343 		case MSG_AUTHORIZATION_CHANGED:
344 			_UpdateAuthorization();
345 			break;
346 
347 		case MSG_CATEGORIES_LIST_CHANGED:
348 			fFilterView->AdoptModel(fModel);
349 			break;
350 
351 		case MSG_SHOW_FEATURED_PACKAGES:
352 			// check to see if we aren't already on the current tab
353 			if (fListTabs->Selection() ==
354 					(fModel.ShowFeaturedPackages() ? 0 : 1))
355 				break;
356 			{
357 				BAutolock locker(fModel.Lock());
358 				fModel.SetShowFeaturedPackages(
359 					fListTabs->Selection() == 0);
360 			}
361 			_AdoptModel();
362 			break;
363 
364 		case MSG_SHOW_AVAILABLE_PACKAGES:
365 			{
366 				BAutolock locker(fModel.Lock());
367 				fModel.SetShowAvailablePackages(
368 					!fModel.ShowAvailablePackages());
369 			}
370 			_AdoptModel();
371 			break;
372 
373 		case MSG_SHOW_INSTALLED_PACKAGES:
374 			{
375 				BAutolock locker(fModel.Lock());
376 				fModel.SetShowInstalledPackages(
377 					!fModel.ShowInstalledPackages());
378 			}
379 			_AdoptModel();
380 			break;
381 
382 		case MSG_SHOW_SOURCE_PACKAGES:
383 			{
384 				BAutolock locker(fModel.Lock());
385 				fModel.SetShowSourcePackages(!fModel.ShowSourcePackages());
386 			}
387 			_AdoptModel();
388 			break;
389 
390 		case MSG_SHOW_DEVELOP_PACKAGES:
391 			{
392 				BAutolock locker(fModel.Lock());
393 				fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages());
394 			}
395 			_AdoptModel();
396 			break;
397 
398 			// this may be triggered by, for example, a user rating being added
399 			// or having been altered.
400 		case MSG_SERVER_DATA_CHANGED:
401 		{
402 			BString name;
403 			if (message->FindString("name", &name) == B_OK) {
404 				BAutolock locker(fModel.Lock());
405 				if (fPackageInfoView->Package()->Name() == name) {
406 					_PopulatePackageAsync(true);
407 				} else {
408 					if (Logger::IsDebugEnabled()) {
409 						printf("pkg [%s] is updated on the server, but is "
410 							"not selected so will not be updated.\n",
411 							name.String());
412 					}
413 				}
414 			}
415         	break;
416         }
417 
418 		case MSG_PACKAGE_SELECTED:
419 		{
420 			BString name;
421 			if (message->FindString("name", &name) == B_OK) {
422 				BAutolock locker(fModel.Lock());
423 				int count = fVisiblePackages.CountItems();
424 				for (int i = 0; i < count; i++) {
425 					const PackageInfoRef& package
426 						= fVisiblePackages.ItemAtFast(i);
427 					if (package.Get() != NULL && package->Name() == name) {
428 						locker.Unlock();
429 						_AdoptPackage(package);
430 						break;
431 					}
432 				}
433 			} else {
434 				_ClearPackage();
435 			}
436 			break;
437 		}
438 
439 		case MSG_CATEGORY_SELECTED:
440 		{
441 			BString code;
442 			if (message->FindString("code", &code) != B_OK)
443 				code = "";
444 			{
445 				BAutolock locker(fModel.Lock());
446 				fModel.SetCategory(code);
447 			}
448 			_AdoptModel();
449 			break;
450 		}
451 
452 		case MSG_DEPOT_SELECTED:
453 		{
454 			BString name;
455 			if (message->FindString("name", &name) != B_OK)
456 				name = "";
457 			{
458 				BAutolock locker(fModel.Lock());
459 				fModel.SetDepot(name);
460 			}
461 			_AdoptModel();
462 			_UpdateAvailableRepositories();
463 			break;
464 		}
465 
466 		case MSG_SEARCH_TERMS_MODIFIED:
467 		{
468 			// TODO: Do this with a delay!
469 			BString searchTerms;
470 			if (message->FindString("search terms", &searchTerms) != B_OK)
471 				searchTerms = "";
472 			{
473 				BAutolock locker(fModel.Lock());
474 				fModel.SetSearchTerms(searchTerms);
475 			}
476 			_AdoptModel();
477 			break;
478 		}
479 
480 		case MSG_PACKAGE_CHANGED:
481 		{
482 			PackageInfo* info;
483 			if (message->FindPointer("package", (void**)&info) == B_OK) {
484 				PackageInfoRef ref(info, true);
485 				uint32 changes;
486 				if (message->FindUInt32("changes", &changes) != B_OK)
487 					changes = 0;
488 				if ((changes & PKG_CHANGED_STATE) != 0) {
489 					BAutolock locker(fModel.Lock());
490 					fModel.SetPackageState(ref, ref->State());
491 				}
492 
493 				// Asynchronous updates to the package information
494 				// can mean that the package needs to be added or
495 				// removed to/from the visible packages when the current
496 				// filter parameters are re-evaluated on this package.
497 				bool wasVisible = fVisiblePackages.Contains(ref);
498 				bool isVisible;
499 				{
500 					BAutolock locker(fModel.Lock());
501 					// The package didn't get a chance yet to be in the
502 					// visible package list
503 					isVisible = fModel.MatchesFilter(ref);
504 
505 					// Transfer this single package, otherwise we miss
506 					// other packages if they appear or disappear along
507 					// with this one before receive a notification for
508 					// them.
509 					if (isVisible) {
510 						fVisiblePackages.Add(ref);
511 					} else if (wasVisible)
512 						fVisiblePackages.Remove(ref);
513 				}
514 
515 				if (wasVisible != isVisible) {
516 					if (!isVisible) {
517 						fPackageListView->RemovePackage(ref);
518 						fFeaturedPackagesView->RemovePackage(ref);
519 					} else {
520 						fPackageListView->AddPackage(ref);
521 						if (ref->IsProminent())
522 							fFeaturedPackagesView->AddPackage(ref);
523 					}
524 				}
525 
526 				if (!fSinglePackageMode && (changes & PKG_CHANGED_STATE) != 0)
527 					fWorkStatusView->PackageStatusChanged(ref);
528 			}
529 			break;
530 		}
531 
532 		case MSG_RATE_PACKAGE:
533 			_RatePackage();
534 			break;
535 
536 		case MSG_SHOW_SCREENSHOT:
537 			_ShowScreenshot();
538 			break;
539 
540 		case MSG_PACKAGE_WORKER_BUSY:
541 		{
542 			BString reason;
543 			status_t status = message->FindString("reason", &reason);
544 			if (status != B_OK)
545 				break;
546 			if (!fSinglePackageMode)
547 				fWorkStatusView->SetBusy(reason);
548 			break;
549 		}
550 
551 		case MSG_PACKAGE_WORKER_IDLE:
552 			if (!fSinglePackageMode)
553 				fWorkStatusView->SetIdle();
554 			break;
555 
556 		case MSG_ADD_VISIBLE_PACKAGES:
557 		{
558 			struct SemaphoreReleaser {
559 				SemaphoreReleaser(sem_id semaphore)
560 					:
561 					fSemaphore(semaphore)
562 				{ }
563 
564 				~SemaphoreReleaser() { release_sem(fSemaphore); }
565 
566 				sem_id fSemaphore;
567 			};
568 
569 			// Make sure acknowledge semaphore is released even on error,
570 			// so the worker thread won't be blocked
571 			SemaphoreReleaser acknowledger(fShowPackagesAcknowledgeSem);
572 
573 			int32 numPackages = 0;
574 			type_code unused;
575 			status_t status = message->GetInfo("package_ref", &unused,
576 				&numPackages);
577 			if (status != B_OK || numPackages == 0)
578 				break;
579 
580 			int32 listID = 0;
581 			status = message->FindInt32("list_id", &listID);
582 			if (status != B_OK)
583 				break;
584 			if (listID != atomic_get(&fPackagesToShowListID)) {
585 				// list is outdated, ignore
586 				break;
587 			}
588 
589 			for (int i = 0; i < numPackages; i++) {
590 				PackageInfo* packageRaw = NULL;
591 				status = message->FindPointer("package_ref", i,
592 					(void**)&packageRaw);
593 				if (status != B_OK)
594 					break;
595 				PackageInfoRef package(packageRaw, true);
596 
597 				fPackageListView->AddPackage(package);
598 				if (package->IsProminent())
599 					fFeaturedPackagesView->AddPackage(package);
600 			}
601 			break;
602 		}
603 
604 		case MSG_UPDATE_SELECTED_PACKAGE:
605 		{
606 			const PackageInfoRef& selectedPackage = fPackageInfoView->Package();
607 			fFeaturedPackagesView->SelectPackage(selectedPackage, true);
608 			fPackageListView->SelectPackage(selectedPackage);
609 
610 			AutoLocker<BLocker> modelLocker(fModel.Lock());
611 			if (!fVisiblePackages.Contains(fPackageInfoView->Package()))
612 				fPackageInfoView->Clear();
613 			break;
614 		}
615 
616 		default:
617 			BWindow::MessageReceived(message);
618 			break;
619 	}
620 }
621 
622 
623 void
624 MainWindow::StoreSettings(BMessage& settings) const
625 {
626 	settings.AddRect(_WindowFrameName(), Frame());
627 	if (!fSinglePackageMode) {
628 		settings.AddRect("window frame", Frame());
629 
630 		BMessage columnSettings;
631 		fPackageListView->SaveState(&columnSettings);
632 
633 		settings.AddMessage("column settings", &columnSettings);
634 
635 		settings.AddBool("show featured packages",
636 			fModel.ShowFeaturedPackages());
637 		settings.AddBool("show available packages",
638 			fModel.ShowAvailablePackages());
639 		settings.AddBool("show installed packages",
640 			fModel.ShowInstalledPackages());
641 		settings.AddBool("show develop packages", fModel.ShowDevelopPackages());
642 		settings.AddBool("show source packages", fModel.ShowSourcePackages());
643 	}
644 
645 	settings.AddString("username", fModel.Username());
646 }
647 
648 
649 void
650 MainWindow::PackageChanged(const PackageInfoEvent& event)
651 {
652 	uint32 watchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE;
653 	if ((event.Changes() & watchedChanges) != 0) {
654 		PackageInfoRef ref(event.Package());
655 		BMessage message(MSG_PACKAGE_CHANGED);
656 		message.AddPointer("package", ref.Get());
657 		message.AddUInt32("changes", event.Changes());
658 		ref.Detach();
659 			// reference needs to be released by MessageReceived();
660 		PostMessage(&message);
661 	}
662 }
663 
664 
665 status_t
666 MainWindow::SchedulePackageActions(PackageActionList& list)
667 {
668 	AutoLocker<BLocker> lock(&fPendingActionsLock);
669 	for (int32 i = 0; i < list.CountItems(); i++) {
670 		if (!fPendingActions.Add(list.ItemAtFast(i)))
671 			return B_NO_MEMORY;
672 	}
673 
674 	return release_sem_etc(fPendingActionsSem, list.CountItems(), 0);
675 }
676 
677 
678 Model*
679 MainWindow::GetModel()
680 {
681 	return &fModel;
682 }
683 
684 
685 void
686 MainWindow::_BuildMenu(BMenuBar* menuBar)
687 {
688 	BMenu* menu = new BMenu(B_TRANSLATE("Tools"));
689 	fRefreshRepositoriesItem = new BMenuItem(
690 		B_TRANSLATE("Refresh repositories"), new BMessage(MSG_REFRESH_REPOS));
691 	menu->AddItem(fRefreshRepositoriesItem);
692 	menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories"
693 		B_UTF8_ELLIPSIS), new BMessage(MSG_MANAGE_REPOS)));
694 	menu->AddItem(new BMenuItem(B_TRANSLATE("Check for updates"
695 		B_UTF8_ELLIPSIS), new BMessage(MSG_SOFTWARE_UPDATER)));
696 
697 	menuBar->AddItem(menu);
698 
699 	fRepositoryMenu = new BMenu(B_TRANSLATE("Repositories"));
700 	menuBar->AddItem(fRepositoryMenu);
701 
702 	menu = new BMenu(B_TRANSLATE("Show"));
703 
704 	fShowAvailablePackagesItem = new BMenuItem(
705 		B_TRANSLATE("Available packages"),
706 		new BMessage(MSG_SHOW_AVAILABLE_PACKAGES));
707 	menu->AddItem(fShowAvailablePackagesItem);
708 
709 	fShowInstalledPackagesItem = new BMenuItem(
710 		B_TRANSLATE("Installed packages"),
711 		new BMessage(MSG_SHOW_INSTALLED_PACKAGES));
712 	menu->AddItem(fShowInstalledPackagesItem);
713 
714 	menu->AddSeparatorItem();
715 
716 	fShowDevelopPackagesItem = new BMenuItem(
717 		B_TRANSLATE("Develop packages"),
718 		new BMessage(MSG_SHOW_DEVELOP_PACKAGES));
719 	menu->AddItem(fShowDevelopPackagesItem);
720 
721 	fShowSourcePackagesItem = new BMenuItem(
722 		B_TRANSLATE("Source packages"),
723 		new BMessage(MSG_SHOW_SOURCE_PACKAGES));
724 	menu->AddItem(fShowSourcePackagesItem);
725 
726 	menuBar->AddItem(menu);
727 }
728 
729 
730 void
731 MainWindow::_BuildUserMenu(BMenuBar* menuBar)
732 {
733 	fUserMenu = new BMenu(B_TRANSLATE("Not logged in"));
734 
735 	fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS),
736 		new BMessage(MSG_LOG_IN));
737 	fUserMenu->AddItem(fLogInItem);
738 
739 	fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"),
740 		new BMessage(MSG_LOG_OUT));
741 	fUserMenu->AddItem(fLogOutItem);
742 
743 	fLogOutItem = new BMenuItem(B_TRANSLATE("View latest user usage conditions"
744 		B_UTF8_ELLIPSIS),
745 		new BMessage(MSG_VIEW_LATEST_USER_USAGE_CONDITIONS));
746 	fUserMenu->AddItem(fLogOutItem);
747 
748 	menuBar->AddItem(fUserMenu);
749 }
750 
751 
752 void
753 MainWindow::_RestoreUserName(const BMessage& settings)
754 {
755 	BString username;
756 	if (settings.FindString("username", &username) == B_OK
757 		&& username.Length() > 0) {
758 		fModel.SetUsername(username);
759 	}
760 }
761 
762 
763 const char*
764 MainWindow::_WindowFrameName() const
765 {
766 	if (fSinglePackageMode)
767 		return "small window frame";
768 
769 	return "window frame";
770 }
771 
772 
773 void
774 MainWindow::_RestoreWindowFrame(const BMessage& settings)
775 {
776 	BRect frame = Frame();
777 
778 	BRect windowFrame;
779 	bool fromSettings = false;
780 	if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) {
781 		frame = windowFrame;
782 		fromSettings = true;
783 	} else if (!fSinglePackageMode) {
784 		// Resize to occupy a certain screen size
785 		BRect screenFrame = BScreen(this).Frame();
786 		float width = frame.Width();
787 		float height = frame.Height();
788 		if (width < screenFrame.Width() * .666f
789 			&& height < screenFrame.Height() * .666f) {
790 			frame.bottom = frame.top + screenFrame.Height() * .666f;
791 			frame.right = frame.left
792 				+ std::min(screenFrame.Width() * .666f, height * 7 / 5);
793 		}
794 	}
795 
796 	MoveTo(frame.LeftTop());
797 	ResizeTo(frame.Width(), frame.Height());
798 
799 	if (fromSettings)
800 		MoveOnScreen();
801 	else
802 		CenterOnScreen();
803 }
804 
805 
806 void
807 MainWindow::_InitWorkerThreads()
808 {
809 	fPendingActionsSem = create_sem(0, "PendingPackageActions");
810 	if (fPendingActionsSem >= 0) {
811 		fPendingActionsWorker = spawn_thread(&_PackageActionWorker,
812 			"Planet Express", B_NORMAL_PRIORITY, this);
813 		if (fPendingActionsWorker >= 0)
814 			resume_thread(fPendingActionsWorker);
815 	} else
816 		fPendingActionsWorker = -1;
817 
818 	fPackageToPopulateSem = create_sem(0, "PopulatePackage");
819 	if (fPackageToPopulateSem >= 0) {
820 		fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker,
821 			"Package Populator", B_NORMAL_PRIORITY, this);
822 		if (fPopulatePackageWorker >= 0)
823 			resume_thread(fPopulatePackageWorker);
824 	} else
825 		fPopulatePackageWorker = -1;
826 
827 	fNewPackagesToShowSem = create_sem(0, "ShowPackages");
828 	fShowPackagesAcknowledgeSem = create_sem(0, "ShowPackagesAck");
829 	if (fNewPackagesToShowSem >= 0 && fShowPackagesAcknowledgeSem >= 0) {
830 		fShowPackagesWorker = spawn_thread(&_PackagesToShowWorker,
831 			"Good news everyone", B_NORMAL_PRIORITY, this);
832 		if (fShowPackagesWorker >= 0)
833 			resume_thread(fShowPackagesWorker);
834 	} else
835 		fShowPackagesWorker = -1;
836 }
837 
838 
839 void
840 MainWindow::_AdoptModel()
841 {
842 	{
843 		AutoLocker<BLocker> modelLocker(fModel.Lock());
844 		fVisiblePackages = fModel.CreatePackageList();
845 		AutoLocker<BLocker> listLocker(fPackagesToShowListLock);
846 		fPackagesToShowList = fVisiblePackages;
847 		atomic_add(&fPackagesToShowListID, 1);
848 	}
849 
850 	fFeaturedPackagesView->Clear();
851 	fPackageListView->Clear();
852 
853 	release_sem(fNewPackagesToShowSem);
854 
855 	BAutolock locker(fModel.Lock());
856 	fShowAvailablePackagesItem->SetMarked(fModel.ShowAvailablePackages());
857 	fShowInstalledPackagesItem->SetMarked(fModel.ShowInstalledPackages());
858 	fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages());
859 	fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages());
860 
861 	if (fModel.ShowFeaturedPackages())
862 		fListTabs->Select(0);
863 	else
864 		fListTabs->Select(1);
865 
866 	fFilterView->AdoptModel(fModel);
867 }
868 
869 
870 void
871 MainWindow::_AdoptPackage(const PackageInfoRef& package)
872 {
873 	{
874 		BAutolock locker(fModel.Lock());
875 		fPackageInfoView->SetPackage(package);
876 
877 		if (fFeaturedPackagesView != NULL)
878 			fFeaturedPackagesView->SelectPackage(package);
879 		if (fPackageListView != NULL)
880 			fPackageListView->SelectPackage(package);
881 	}
882 
883 	_PopulatePackageAsync(false);
884 }
885 
886 
887 void
888 MainWindow::_ClearPackage()
889 {
890 	fPackageInfoView->Clear();
891 }
892 
893 
894 void
895 MainWindow::_StopBulkLoad()
896 {
897 	AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock);
898 
899 	if (fBulkLoadProcessCoordinator != NULL) {
900 		printf("will stop full update process coordinator\n");
901 		fBulkLoadProcessCoordinator->Stop();
902 	}
903 }
904 
905 
906 void
907 MainWindow::_StartBulkLoad(bool force)
908 {
909 	AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock);
910 
911 	if (fBulkLoadProcessCoordinator == NULL) {
912 		fBulkLoadProcessCoordinator
913 			= ProcessCoordinatorFactory::CreateBulkLoadCoordinator(
914 				this,
915 					// PackageInfoListener
916 				this,
917 					// ProcessCoordinatorListener
918 				&fModel, force);
919 		fBulkLoadProcessCoordinator->Start();
920 		fRefreshRepositoriesItem->SetEnabled(false);
921 	}
922 }
923 
924 
925 /*! This method is called when there is some change in the bulk load process.
926     A change may mean that a new process has started / stopped etc... or it
927     may mean that the entire coordinator has finished.
928 */
929 
930 void
931 MainWindow::CoordinatorChanged(ProcessCoordinatorState& coordinatorState)
932 {
933 	AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock);
934 
935 	if (fBulkLoadProcessCoordinator == coordinatorState.Coordinator()) {
936 		if (!coordinatorState.IsRunning())
937 			_BulkLoadProcessCoordinatorFinished(coordinatorState);
938 		else {
939 			_NotifyWorkStatusChange(coordinatorState.Message(),
940 				coordinatorState.Progress());
941 				// show the progress to the user.
942 		}
943 	} else {
944 		if (Logger::IsInfoEnabled()) {
945 			printf("unknown process coordinator changed\n");
946 		}
947 	}
948 }
949 
950 
951 void
952 MainWindow::_BulkLoadProcessCoordinatorFinished(
953 	ProcessCoordinatorState& coordinatorState)
954 {
955 	if (coordinatorState.ErrorStatus() != B_OK) {
956 		AppUtils::NotifySimpleError(
957 			B_TRANSLATE("Package Update Error"),
958 			B_TRANSLATE("While updating package data, a problem has arisen "
959 				"that may cause data to be outdated or missing from the "
960 				"application's display. Additional details regarding this "
961 				"problem may be able to be obtained from the application "
962 				"logs."));
963 	}
964 	BMessenger messenger(this);
965 	messenger.SendMessage(MSG_BULK_LOAD_DONE);
966 	// it is safe to delete the coordinator here because it is already known
967 	// that all of the processes have completed and their threads will have
968 	// exited safely by this point.
969 	delete fBulkLoadProcessCoordinator;
970 	fBulkLoadProcessCoordinator = NULL;
971 	fRefreshRepositoriesItem->SetEnabled(true);
972 }
973 
974 
975 void
976 MainWindow::_BulkLoadCompleteReceived()
977 {
978 	_AdoptModel();
979 	_UpdateAvailableRepositories();
980 	fWorkStatusView->SetIdle();
981 }
982 
983 
984 /*! Sends off a message to the Window so that it can change the status view
985     on the front-end in the UI thread.
986 */
987 
988 void
989 MainWindow::_NotifyWorkStatusChange(const BString& text, float progress)
990 {
991 	BMessage message(MSG_WORK_STATUS_CHANGE);
992 
993 	if (!text.IsEmpty())
994 		message.AddString(KEY_WORK_STATUS_TEXT, text);
995 	message.AddFloat(KEY_WORK_STATUS_PROGRESS, progress);
996 
997 	this->PostMessage(&message, this);
998 }
999 
1000 
1001 void
1002 MainWindow::_HandleWorkStatusChangeMessageReceived(const BMessage* message)
1003 {
1004 	BString text;
1005 	float progress;
1006 
1007 	if (message->FindString(KEY_WORK_STATUS_TEXT, &text) == B_OK)
1008 		fWorkStatusView->SetText(text);
1009 
1010 	if (message->FindFloat(KEY_WORK_STATUS_PROGRESS, &progress) == B_OK)
1011 		fWorkStatusView->SetProgress(progress);
1012 }
1013 
1014 
1015 status_t
1016 MainWindow::_PackageActionWorker(void* arg)
1017 {
1018 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1019 
1020 	while (acquire_sem(window->fPendingActionsSem) == B_OK) {
1021 		PackageActionRef ref;
1022 		{
1023 			AutoLocker<BLocker> lock(&window->fPendingActionsLock);
1024 			ref = window->fPendingActions.ItemAt(0);
1025 			if (ref.Get() == NULL)
1026 				break;
1027 			window->fPendingActions.Remove(0);
1028 		}
1029 
1030 		BMessenger messenger(window);
1031 		BMessage busyMessage(MSG_PACKAGE_WORKER_BUSY);
1032 		BString text(ref->Label());
1033 		text << B_UTF8_ELLIPSIS;
1034 		busyMessage.AddString("reason", text);
1035 
1036 		messenger.SendMessage(&busyMessage);
1037 		ref->Perform();
1038 		messenger.SendMessage(MSG_PACKAGE_WORKER_IDLE);
1039 	}
1040 
1041 	return 0;
1042 }
1043 
1044 
1045 /*! This method will cause the package to have its data refreshed from
1046     the server application.  The refresh happens in the background; this method
1047     is asynchronous.
1048 */
1049 
1050 void
1051 MainWindow::_PopulatePackageAsync(bool forcePopulate)
1052 {
1053 		// Trigger asynchronous package population from the web-app
1054 	{
1055 		AutoLocker<BLocker> lock(&fPackageToPopulateLock);
1056 		fPackageToPopulate = fPackageInfoView->Package();
1057 		fForcePopulatePackage = forcePopulate;
1058 	}
1059 	release_sem_etc(fPackageToPopulateSem, 1, 0);
1060 
1061 	if (Logger::IsDebugEnabled()) {
1062 		printf("pkg [%s] will be updated from the server.\n",
1063 			fPackageToPopulate->Name().String());
1064 	}
1065 }
1066 
1067 
1068 /*! This method will run in the background.  The thread will block until there
1069     is a package to be updated.  When the thread unblocks, it will update the
1070     package with information from the server.
1071 */
1072 
1073 status_t
1074 MainWindow::_PopulatePackageWorker(void* arg)
1075 {
1076 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1077 
1078 	while (acquire_sem(window->fPackageToPopulateSem) == B_OK) {
1079 		PackageInfoRef package;
1080 		bool force;
1081 		{
1082 			AutoLocker<BLocker> lock(&window->fPackageToPopulateLock);
1083 			package = window->fPackageToPopulate;
1084 			force = window->fForcePopulatePackage;
1085 		}
1086 
1087 		if (package.Get() != NULL) {
1088 			uint32 populateFlags = Model::POPULATE_USER_RATINGS
1089 				| Model::POPULATE_SCREEN_SHOTS
1090 				| Model::POPULATE_CHANGELOG;
1091 
1092 			if (force)
1093 				populateFlags |= Model::POPULATE_FORCE;
1094 
1095 			window->fModel.PopulatePackage(package, populateFlags);
1096 
1097 			if (Logger::IsDebugEnabled()) {
1098 				printf("populating package [%s]\n",
1099 					package->Name().String());
1100 			}
1101 		}
1102 	}
1103 
1104 	return 0;
1105 }
1106 
1107 
1108 /* static */ status_t
1109 MainWindow::_PackagesToShowWorker(void* arg)
1110 {
1111 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1112 
1113 	while (acquire_sem(window->fNewPackagesToShowSem) == B_OK) {
1114 		PackageList packageList;
1115 		int32 listID = 0;
1116 		{
1117 			AutoLocker<BLocker> lock(&window->fPackagesToShowListLock);
1118 			packageList = window->fPackagesToShowList;
1119 			listID = atomic_get(&window->fPackagesToShowListID);
1120 			window->fPackagesToShowList.Clear();
1121 		}
1122 
1123 		// Add packages to list views in batches of kPackagesPerUpdate so we
1124 		// don't block the window thread for long with each iteration
1125 		enum {
1126 			kPackagesPerUpdate = 20
1127 		};
1128 		uint32 packagesInMessage = 0;
1129 		BMessage message(MSG_ADD_VISIBLE_PACKAGES);
1130 		BMessenger messenger(window);
1131 		bool listIsOutdated = false;
1132 
1133 		for (int i = 0; i < packageList.CountItems(); i++) {
1134 			const PackageInfoRef& package = packageList.ItemAtFast(i);
1135 
1136 			if (packagesInMessage >= kPackagesPerUpdate) {
1137 				if (listID != atomic_get(&window->fPackagesToShowListID)) {
1138 					// The model was changed again in the meantime, and thus
1139 					// our package list isn't current anymore. Send no further
1140 					// messags based on this list and go back to start.
1141 					listIsOutdated = true;
1142 					break;
1143 				}
1144 
1145 				message.AddInt32("list_id", listID);
1146 				messenger.SendMessage(&message);
1147 				message.MakeEmpty();
1148 				packagesInMessage = 0;
1149 
1150 				// Don't spam the window's message queue, which would make it
1151 				// unresponsive (i.e. allows UI messages to get in between our
1152 				// messages). When it has processed the message we just sent,
1153 				// it will let us know by releasing the semaphore.
1154 				acquire_sem(window->fShowPackagesAcknowledgeSem);
1155 			}
1156 			package->AcquireReference();
1157 			message.AddPointer("package_ref", package.Get());
1158 			packagesInMessage++;
1159 		}
1160 
1161 		if (listIsOutdated)
1162 			continue;
1163 
1164 		// Send remaining package infos, if any, which didn't make it into
1165 		// the last message (count < kPackagesPerUpdate)
1166 		if (packagesInMessage > 0) {
1167 			message.AddInt32("list_id", listID);
1168 			messenger.SendMessage(&message);
1169 			acquire_sem(window->fShowPackagesAcknowledgeSem);
1170 		}
1171 
1172 		// Update selected package in list views
1173 		messenger.SendMessage(MSG_UPDATE_SELECTED_PACKAGE);
1174 	}
1175 
1176 	return 0;
1177 }
1178 
1179 
1180 void
1181 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage)
1182 {
1183 	UserLoginWindow* window = new UserLoginWindow(this,
1184 		BRect(0, 0, 500, 400), fModel);
1185 
1186 	if (onSuccessMessage.what != 0)
1187 		window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage);
1188 
1189 	window->Show();
1190 }
1191 
1192 
1193 void
1194 MainWindow::_UpdateAuthorization()
1195 {
1196 	BString username(fModel.Username());
1197 	bool hasUser = !username.IsEmpty();
1198 
1199 	if (fLogOutItem != NULL)
1200 		fLogOutItem->SetEnabled(hasUser);
1201 	if (fLogInItem != NULL) {
1202 		if (hasUser)
1203 			fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS));
1204 		else
1205 			fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS));
1206 	}
1207 
1208 	if (fUserMenu != NULL) {
1209 		BString label;
1210 		if (username.Length() == 0) {
1211 			label = B_TRANSLATE("Not logged in");
1212 		} else {
1213 			label = B_TRANSLATE("Logged in as %User%");
1214 			label.ReplaceAll("%User%", username);
1215 		}
1216 		fUserMenu->Superitem()->SetLabel(label);
1217 	}
1218 }
1219 
1220 
1221 void
1222 MainWindow::_UpdateAvailableRepositories()
1223 {
1224 	fRepositoryMenu->RemoveItems(0, fRepositoryMenu->CountItems(), true);
1225 
1226 	fRepositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All repositories"),
1227 		new BMessage(MSG_DEPOT_SELECTED)));
1228 
1229 	fRepositoryMenu->AddItem(new BSeparatorItem());
1230 
1231 	bool foundSelectedDepot = false;
1232 	const DepotList& depots = fModel.Depots();
1233 	for (int i = 0; i < depots.CountItems(); i++) {
1234 		const DepotInfo& depot = depots.ItemAtFast(i);
1235 
1236 		if (depot.Name().Length() != 0) {
1237 			BMessage* message = new BMessage(MSG_DEPOT_SELECTED);
1238 			message->AddString("name", depot.Name());
1239 			BMenuItem* item = new BMenuItem(depot.Name(), message);
1240 			fRepositoryMenu->AddItem(item);
1241 
1242 			if (depot.Name() == fModel.Depot()) {
1243 				item->SetMarked(true);
1244 				foundSelectedDepot = true;
1245 			}
1246 		}
1247 	}
1248 
1249 	if (!foundSelectedDepot)
1250 		fRepositoryMenu->ItemAt(0)->SetMarked(true);
1251 }
1252 
1253 
1254 bool
1255 MainWindow::_SelectedPackageHasWebAppRepositoryCode()
1256 {
1257 	const PackageInfoRef& package = fPackageInfoView->Package();
1258 	const BString depotName = package->DepotName();
1259 
1260 	if (depotName.IsEmpty()) {
1261 		if (Logger::IsDebugEnabled()) {
1262 			printf("the package [%s] has no depot name\n",
1263 				package->Name().String());
1264 		}
1265 	} else {
1266 		const DepotInfo* depot = fModel.DepotForName(depotName);
1267 
1268 		if (depot == NULL) {
1269 			printf("the depot [%s] was not able to be found\n",
1270 				depotName.String());
1271 		} else {
1272 			BString repositoryCode = depot->WebAppRepositoryCode();
1273 
1274 			if (repositoryCode.IsEmpty()) {
1275 				printf("the depot [%s] has no web app repository code\n",
1276 					depotName.String());
1277 			} else {
1278 				return true;
1279 			}
1280 		}
1281 	}
1282 
1283 	return false;
1284 }
1285 
1286 
1287 void
1288 MainWindow::_RatePackage()
1289 {
1290 	if (!_SelectedPackageHasWebAppRepositoryCode()) {
1291 		BAlert* alert = new(std::nothrow) BAlert(
1292 			B_TRANSLATE("Rating not possible"),
1293 			B_TRANSLATE("This package doesn't seem to be on the HaikuDepot "
1294 				"Server, so it's not possible to create a new rating "
1295 				"or edit an existing rating."),
1296 			B_TRANSLATE("OK"));
1297 		alert->Go();
1298     	return;
1299 	}
1300 
1301 	if (fModel.Username().IsEmpty()) {
1302 		BAlert* alert = new(std::nothrow) BAlert(
1303 			B_TRANSLATE("Not logged in"),
1304 			B_TRANSLATE("You need to be logged into an account before you "
1305 				"can rate packages."),
1306 			B_TRANSLATE("Cancel"),
1307 			B_TRANSLATE("Login or Create account"));
1308 
1309 		if (alert == NULL)
1310 			return;
1311 
1312 		int32 choice = alert->Go();
1313 		if (choice == 1)
1314 			_OpenLoginWindow(BMessage(MSG_RATE_PACKAGE));
1315 		return;
1316 	}
1317 
1318 	// TODO: Allow only one RatePackageWindow
1319 	// TODO: Mechanism for remembering the window frame
1320 	RatePackageWindow* window = new RatePackageWindow(this,
1321 		BRect(0, 0, 500, 400), fModel);
1322 	window->SetPackage(fPackageInfoView->Package());
1323 	window->Show();
1324 }
1325 
1326 
1327 void
1328 MainWindow::_ShowScreenshot()
1329 {
1330 	// TODO: Mechanism for remembering the window frame
1331 	if (fScreenshotWindow == NULL)
1332 		fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400));
1333 
1334 	if (fScreenshotWindow->LockWithTimeout(1000) != B_OK)
1335 		return;
1336 
1337 	fScreenshotWindow->SetPackage(fPackageInfoView->Package());
1338 
1339 	if (fScreenshotWindow->IsHidden())
1340 		fScreenshotWindow->Show();
1341 	else
1342 		fScreenshotWindow->Activate();
1343 
1344 	fScreenshotWindow->Unlock();
1345 }
1346 
1347 
1348 void
1349 MainWindow::_ViewLatestUserUsageConditions()
1350 {
1351 	UserUsageConditionsWindow* window = new UserUsageConditionsWindow(
1352 		this, BRect(0, 0, 500, 400), fModel, LATEST);
1353 	window->Show();
1354 }