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-2024, 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#include "MainWindow.h"
11
12#include <map>
13#include <vector>
14
15#include <stdio.h>
16#include <Alert.h>
17#include <Autolock.h>
18#include <Application.h>
19#include <Button.h>
20#include <Catalog.h>
21#include <CardLayout.h>
22#include <LayoutBuilder.h>
23#include <MenuBar.h>
24#include <MenuItem.h>
25#include <MessageRunner.h>
26#include <Messenger.h>
27#include <Roster.h>
28#include <Screen.h>
29#include <ScrollView.h>
30#include <StringList.h>
31#include <StringView.h>
32#include <TabView.h>
33
34#include "AppUtils.h"
35#include "AutoDeleter.h"
36#include "AutoLocker.h"
37#include "DecisionProvider.h"
38#include "FeaturedPackagesView.h"
39#include "FilterView.h"
40#include "Logger.h"
41#include "PackageInfoView.h"
42#include "PackageListView.h"
43#include "PackageManager.h"
44#include "ProcessCoordinator.h"
45#include "ProcessCoordinatorFactory.h"
46#include "RatePackageWindow.h"
47#include "support.h"
48#include "ScreenshotWindow.h"
49#include "SettingsWindow.h"
50#include "ShuttingDownWindow.h"
51#include "ToLatestUserUsageConditionsWindow.h"
52#include "UserLoginWindow.h"
53#include "UserUsageConditionsWindow.h"
54#include "WorkStatusView.h"
55
56
57#undef B_TRANSLATION_CONTEXT
58#define B_TRANSLATION_CONTEXT "MainWindow"
59
60
61enum {
62	MSG_REFRESH_REPOS						= 'mrrp',
63	MSG_MANAGE_REPOS						= 'mmrp',
64	MSG_SOFTWARE_UPDATER					= 'mswu',
65	MSG_SETTINGS							= 'stgs',
66	MSG_LOG_IN								= 'lgin',
67	MSG_AUTHORIZATION_CHANGED				= 'athc',
68	MSG_CATEGORIES_LIST_CHANGED				= 'clic',
69	MSG_PACKAGE_CHANGED						= 'pchd',
70	MSG_PROCESS_COORDINATOR_CHANGED			= 'pccd',
71	MSG_WORK_STATUS_CHANGE					= 'wsch',
72	MSG_WORK_STATUS_CLEAR					= 'wscl',
73	MSG_INCREMENT_VIEW_COUNTER				= 'icrv',
74	MSG_SCREENSHOT_CACHED					= 'ssca',
75
76	MSG_CHANGE_PACKAGE_LIST_VIEW_MODE		= 'cplm',
77	MSG_SHOW_AVAILABLE_PACKAGES				= 'savl',
78	MSG_SHOW_INSTALLED_PACKAGES				= 'sins',
79	MSG_SHOW_SOURCE_PACKAGES				= 'ssrc',
80	MSG_SHOW_DEVELOP_PACKAGES				= 'sdvl'
81};
82
83#define KEY_ERROR_STATUS				"errorStatus"
84
85const bigtime_t kIncrementViewCounterDelayMicros = 3 * 1000 * 1000;
86
87#define TAB_PROMINENT_PACKAGES	0
88#define TAB_ALL_PACKAGES		1
89
90using namespace BPackageKit;
91using namespace BPackageKit::BManager::BPrivate;
92
93
94typedef std::map<BString, PackageInfoRef> PackageInfoMap;
95
96
97struct RefreshWorkerParameters {
98	MainWindow* window;
99	bool forceRefresh;
100
101	RefreshWorkerParameters(MainWindow* window, bool forceRefresh)
102		:
103		window(window),
104		forceRefresh(forceRefresh)
105	{
106	}
107};
108
109
110class MainWindowModelListener : public ModelListener {
111public:
112	MainWindowModelListener(const BMessenger& messenger)
113		:
114		fMessenger(messenger)
115	{
116	}
117
118	virtual void AuthorizationChanged()
119	{
120		if (fMessenger.IsValid())
121			fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED);
122	}
123
124	virtual void CategoryListChanged()
125	{
126		if (fMessenger.IsValid())
127			fMessenger.SendMessage(MSG_CATEGORIES_LIST_CHANGED);
128	}
129
130	virtual void ScreenshotCached(const ScreenshotCoordinate& coordinate)
131	{
132		if (fMessenger.IsValid()) {
133			BMessage message(MSG_SCREENSHOT_CACHED);
134			if (coordinate.Archive(&message) != B_OK)
135				debugger("unable to serialize a screenshot coordinate");
136			fMessenger.SendMessage(&message);
137		}
138	}
139
140private:
141	BMessenger	fMessenger;
142};
143
144
145class MainWindowPackageInfoListener : public PackageInfoListener {
146public:
147	MainWindowPackageInfoListener(MainWindow* mainWindow)
148		:
149		fMainWindow(mainWindow)
150	{
151	}
152
153	~MainWindowPackageInfoListener()
154	{
155	}
156
157private:
158	// PackageInfoListener
159	virtual	void PackageChanged(const PackageInfoEvent& event)
160	{
161		fMainWindow->PackageChanged(event);
162	}
163
164private:
165	MainWindow*	fMainWindow;
166};
167
168
169MainWindow::MainWindow(const BMessage& settings)
170	:
171	BWindow(BRect(50, 50, 650, 550), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
172		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
173		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
174	fScreenshotWindow(NULL),
175	fShuttingDownWindow(NULL),
176	fUserMenu(NULL),
177	fLogInItem(NULL),
178	fLogOutItem(NULL),
179	fUsersUserUsageConditionsMenuItem(NULL),
180	fModelListener(new MainWindowModelListener(BMessenger(this)), true),
181	fCoordinator(NULL),
182	fShouldCloseWhenNoProcessesToCoordinate(false),
183	fSinglePackageMode(false),
184	fIncrementViewCounterDelayedRunner(NULL)
185{
186	if ((fCoordinatorRunningSem = create_sem(1, "ProcessCoordinatorSem")) < B_OK)
187		debugger("unable to create the process coordinator semaphore");
188
189	fPackageInfoListener = PackageInfoListenerRef(
190		new MainWindowPackageInfoListener(this), true);
191
192	BMenuBar* menuBar = new BMenuBar("Main Menu");
193	_BuildMenu(menuBar);
194
195	BMenuBar* userMenuBar = new BMenuBar("User Menu");
196	_BuildUserMenu(userMenuBar);
197	set_small_font(userMenuBar);
198	userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET,
199		menuBar->MaxSize().height));
200
201	fFilterView = new FilterView();
202	fFeaturedPackagesView = new FeaturedPackagesView(fModel);
203	fPackageListView = new PackageListView(&fModel);
204	fPackageInfoView = new PackageInfoView(&fModel, this);
205
206	fSplitView = new BSplitView(B_VERTICAL, 5.0f);
207
208	fWorkStatusView = new WorkStatusView("work status");
209	fPackageListView->AttachWorkStatusView(fWorkStatusView);
210
211	fListTabs = new TabView(BMessenger(this),
212		BMessage(MSG_CHANGE_PACKAGE_LIST_VIEW_MODE), "list tabs");
213	fListTabs->AddTab(fFeaturedPackagesView);
214	fListTabs->AddTab(fPackageListView);
215
216	BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f)
217		.AddGroup(B_HORIZONTAL, 0.0f)
218			.Add(menuBar, 1.0f)
219			.Add(userMenuBar, 0.0f)
220		.End()
221		.Add(fFilterView)
222		.AddSplit(fSplitView)
223			.AddGroup(B_VERTICAL)
224				.Add(fListTabs)
225				.SetInsets(
226					B_USE_DEFAULT_SPACING, 0.0f,
227					B_USE_DEFAULT_SPACING, 0.0f)
228			.End()
229			.Add(fPackageInfoView)
230		.End()
231		.Add(fWorkStatusView)
232	;
233
234	fSplitView->SetCollapsible(0, false);
235	fSplitView->SetCollapsible(1, false);
236
237	fModel.AddListener(fModelListener);
238
239	BMessage columnSettings;
240	if (settings.FindMessage("column settings", &columnSettings) == B_OK)
241		fPackageListView->LoadState(&columnSettings);
242
243	_RestoreModelSettings(settings);
244	_MaybePromptCanShareAnonymousUserData(settings);
245
246	if (fModel.PackageListViewMode() == PROMINENT)
247		fListTabs->Select(TAB_PROMINENT_PACKAGES);
248	else
249		fListTabs->Select(TAB_ALL_PACKAGES);
250
251	_RestoreNickname(settings);
252	_UpdateAuthorization();
253	_RestoreWindowFrame(settings);
254
255	// start worker threads
256	BPackageRoster().StartWatching(this,
257		B_WATCH_PACKAGE_INSTALLATION_LOCATIONS);
258
259	_InitWorkerThreads();
260	_AdoptModel();
261	_StartBulkLoad();
262}
263
264
265MainWindow::MainWindow(const BMessage& settings, PackageInfoRef& package)
266	:
267	BWindow(BRect(50, 50, 650, 350), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
268		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
269		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
270	fFeaturedPackagesView(NULL),
271	fPackageListView(NULL),
272	fWorkStatusView(NULL),
273	fScreenshotWindow(NULL),
274	fShuttingDownWindow(NULL),
275	fUserMenu(NULL),
276	fLogInItem(NULL),
277	fLogOutItem(NULL),
278	fUsersUserUsageConditionsMenuItem(NULL),
279	fModelListener(new MainWindowModelListener(BMessenger(this)), true),
280	fCoordinator(NULL),
281	fShouldCloseWhenNoProcessesToCoordinate(false),
282	fSinglePackageMode(true),
283	fIncrementViewCounterDelayedRunner(NULL)
284{
285	if ((fCoordinatorRunningSem = create_sem(1, "ProcessCoordinatorSem")) < B_OK)
286		debugger("unable to create the process coordinator semaphore");
287
288	fPackageInfoListener = PackageInfoListenerRef(
289		new MainWindowPackageInfoListener(this), true);
290
291	fFilterView = new FilterView();
292	fPackageInfoView = new PackageInfoView(&fModel, this);
293	fWorkStatusView = new WorkStatusView("work status");
294
295	BLayoutBuilder::Group<>(this, B_VERTICAL)
296		.Add(fPackageInfoView)
297		.Add(fWorkStatusView)
298		.SetInsets(0, B_USE_WINDOW_INSETS, 0, 0)
299	;
300
301	fModel.AddListener(fModelListener);
302
303	// add the single package into the model so that any internal
304	// business logic is able to find the package.
305	DepotInfoRef depot(new DepotInfo("single-pkg-depot"), true);
306	depot->AddPackage(package);
307	fModel.MergeOrAddDepot(depot);
308
309	// Restore settings
310	_RestoreNickname(settings);
311	_UpdateAuthorization();
312	_RestoreWindowFrame(settings);
313
314	fPackageInfoView->SetPackage(package);
315
316	// start worker threads
317	BPackageRoster().StartWatching(this,
318		B_WATCH_PACKAGE_INSTALLATION_LOCATIONS);
319
320	_InitWorkerThreads();
321}
322
323
324MainWindow::~MainWindow()
325{
326	_SpinUntilProcessCoordinatorComplete();
327	delete_sem(fCoordinatorRunningSem);
328	fCoordinatorRunningSem = 0;
329
330	BPackageRoster().StopWatching(this);
331
332	if (fScreenshotWindow != NULL) {
333		if (fScreenshotWindow->Lock())
334			fScreenshotWindow->Quit();
335	}
336
337	if (fShuttingDownWindow != NULL) {
338		if (fShuttingDownWindow->Lock())
339			fShuttingDownWindow->Quit();
340	}
341
342	delete_sem(fPackageToPopulateSem);
343	wait_for_thread(fPopulatePackageWorker, NULL);
344
345	// We must clear the model early to release references.
346	fModel.Clear();
347}
348
349
350bool
351MainWindow::QuitRequested()
352{
353
354	_StopProcessCoordinators();
355
356	// If there are any processes in flight we need to be careful to make
357	// sure that they are cleanly completed before actually quitting.  By
358	// turning on the `fShouldCloseWhenNoProcessesToCoordinate` flag, when
359	// the process coordination has completed then it will detect this and
360	// quit again.
361
362	{
363		AutoLocker<BLocker> lock(&fCoordinatorLock);
364		fShouldCloseWhenNoProcessesToCoordinate = true;
365
366		if (fCoordinator != NULL) {
367			HDINFO("a coordinator is running --> will wait before quitting...");
368
369			if (fShuttingDownWindow == NULL)
370				fShuttingDownWindow = new ShuttingDownWindow(this);
371			fShuttingDownWindow->Show();
372
373			return false;
374		}
375	}
376
377	BMessage settings;
378	StoreSettings(settings);
379	BMessage message(MSG_MAIN_WINDOW_CLOSED);
380	message.AddMessage(KEY_WINDOW_SETTINGS, &settings);
381	be_app->PostMessage(&message);
382
383	if (fShuttingDownWindow != NULL) {
384		if (fShuttingDownWindow->Lock())
385			fShuttingDownWindow->Quit();
386		fShuttingDownWindow = NULL;
387	}
388
389	return true;
390}
391
392
393void
394MainWindow::MessageReceived(BMessage* message)
395{
396	switch (message->what) {
397		case MSG_BULK_LOAD_DONE:
398		{
399			int64 errorStatus64;
400			if (message->FindInt64(KEY_ERROR_STATUS, &errorStatus64) == B_OK)
401				_BulkLoadCompleteReceived((status_t) errorStatus64);
402			else
403				HDERROR("expected [%s] value in message", KEY_ERROR_STATUS);
404			break;
405		}
406		case B_SIMPLE_DATA:
407		case B_REFS_RECEIVED:
408			// TODO: ?
409			break;
410
411		case B_PACKAGE_UPDATE:
412			_HandleExternalPackageUpdateMessageReceived(message);
413			break;
414
415		case MSG_REFRESH_REPOS:
416			_StartBulkLoad(true);
417			break;
418
419		case MSG_WORK_STATUS_CLEAR:
420			_HandleWorkStatusClear();
421			break;
422
423		case MSG_WORK_STATUS_CHANGE:
424			_HandleWorkStatusChangeMessageReceived(message);
425			break;
426
427		case MSG_MANAGE_REPOS:
428			be_roster->Launch("application/x-vnd.Haiku-Repositories");
429			break;
430
431		case MSG_SOFTWARE_UPDATER:
432			be_roster->Launch("application/x-vnd.haiku-softwareupdater");
433			break;
434
435		case MSG_LOG_IN:
436			_OpenLoginWindow(BMessage());
437			break;
438
439		case MSG_SETTINGS:
440			_OpenSettingsWindow();
441			break;
442
443		case MSG_LOG_OUT:
444			fModel.SetNickname("");
445			break;
446
447		case MSG_VIEW_LATEST_USER_USAGE_CONDITIONS:
448			_ViewUserUsageConditions(LATEST);
449			break;
450
451		case MSG_VIEW_USERS_USER_USAGE_CONDITIONS:
452			_ViewUserUsageConditions(USER);
453			break;
454
455		case MSG_AUTHORIZATION_CHANGED:
456			_StartUserVerify();
457			_UpdateAuthorization();
458			break;
459
460		case MSG_SCREENSHOT_CACHED:
461			_HandleScreenshotCached(message);
462			break;
463
464		case MSG_CATEGORIES_LIST_CHANGED:
465			fFilterView->AdoptModel(fModel);
466			break;
467
468		case MSG_CHANGE_PACKAGE_LIST_VIEW_MODE:
469			_HandleChangePackageListViewMode();
470			break;
471
472		case MSG_SHOW_AVAILABLE_PACKAGES:
473			{
474				BAutolock locker(fModel.Lock());
475				fModel.SetShowAvailablePackages(
476					!fModel.ShowAvailablePackages());
477			}
478			_AdoptModel();
479			break;
480
481		case MSG_SHOW_INSTALLED_PACKAGES:
482			{
483				BAutolock locker(fModel.Lock());
484				fModel.SetShowInstalledPackages(
485					!fModel.ShowInstalledPackages());
486			}
487			_AdoptModel();
488			break;
489
490		case MSG_SHOW_SOURCE_PACKAGES:
491			{
492				BAutolock locker(fModel.Lock());
493				fModel.SetShowSourcePackages(!fModel.ShowSourcePackages());
494			}
495			_AdoptModel();
496			break;
497
498		case MSG_SHOW_DEVELOP_PACKAGES:
499			{
500				BAutolock locker(fModel.Lock());
501				fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages());
502			}
503			_AdoptModel();
504			break;
505
506			// this may be triggered by, for example, a user rating being added
507			// or having been altered.
508		case MSG_SERVER_DATA_CHANGED:
509		{
510			BString name;
511			if (message->FindString("name", &name) == B_OK) {
512				BAutolock locker(fModel.Lock());
513				if (fPackageInfoView->Package()->Name() == name) {
514					_PopulatePackageAsync(true);
515				} else {
516					HDDEBUG("pkg [%s] is updated on the server, but is "
517						"not selected so will not be updated.",
518						name.String());
519				}
520			}
521			break;
522		}
523
524		case MSG_INCREMENT_VIEW_COUNTER:
525			_HandleIncrementViewCounter(message);
526			break;
527
528		case MSG_PACKAGE_SELECTED:
529		{
530			BString name;
531			if (message->FindString("name", &name) == B_OK) {
532				PackageInfoRef package;
533				{
534					BAutolock locker(fModel.Lock());
535					package = fModel.PackageForName(name);
536				}
537				if (!package.IsSet() || name != package->Name())
538					debugger("unable to find the named package");
539				else {
540					_AdoptPackage(package);
541					_SetupDelayedIncrementViewCounter(package);
542				}
543			} else {
544				_ClearPackage();
545			}
546			break;
547		}
548
549		case MSG_CATEGORY_SELECTED:
550		{
551			BString code;
552			if (message->FindString("code", &code) != B_OK)
553				code = "";
554			{
555				BAutolock locker(fModel.Lock());
556				fModel.SetCategory(code);
557			}
558			_AdoptModel();
559			break;
560		}
561
562		case MSG_DEPOT_SELECTED:
563		{
564			BString name;
565			if (message->FindString("name", &name) != B_OK)
566				name = "";
567			{
568				BAutolock locker(fModel.Lock());
569				fModel.SetDepot(name);
570			}
571			_AdoptModel();
572			_UpdateAvailableRepositories();
573			break;
574		}
575
576		case MSG_SEARCH_TERMS_MODIFIED:
577		{
578			// TODO: Do this with a delay!
579			BString searchTerms;
580			if (message->FindString("search terms", &searchTerms) != B_OK)
581				searchTerms = "";
582			{
583				BAutolock locker(fModel.Lock());
584				fModel.SetSearchTerms(searchTerms);
585			}
586			_AdoptModel();
587			break;
588		}
589
590		case MSG_PACKAGE_CHANGED:
591		{
592			PackageInfo* info;
593			if (message->FindPointer("package", (void**)&info) == B_OK) {
594				PackageInfoRef ref(info, true);
595				fFeaturedPackagesView->BeginAddRemove();
596				_AddRemovePackageFromLists(ref);
597				fFeaturedPackagesView->EndAddRemove();
598			}
599			break;
600		}
601
602		case MSG_PROCESS_COORDINATOR_CHANGED:
603		{
604			ProcessCoordinatorState state(message);
605			_HandleProcessCoordinatorChanged(state);
606			break;
607		}
608
609		case MSG_RATE_PACKAGE:
610			_RatePackage();
611			break;
612
613		case MSG_SHOW_SCREENSHOT:
614			_ShowScreenshot();
615			break;
616
617		case MSG_PACKAGE_WORKER_BUSY:
618		{
619			BString reason;
620			status_t status = message->FindString("reason", &reason);
621			if (status != B_OK)
622				break;
623			fWorkStatusView->SetBusy(reason);
624			break;
625		}
626
627		case MSG_PACKAGE_WORKER_IDLE:
628			fWorkStatusView->SetIdle();
629			break;
630
631		case MSG_USER_USAGE_CONDITIONS_NOT_LATEST:
632		{
633			BMessage userDetailMsg;
634			if (message->FindMessage("userDetail", &userDetailMsg) != B_OK) {
635				debugger("expected the [userDetail] data to be carried in the "
636					"message.");
637			}
638			UserDetail userDetail(&userDetailMsg);
639			_HandleUserUsageConditionsNotLatest(userDetail);
640			break;
641		}
642
643		default:
644			BWindow::MessageReceived(message);
645			break;
646	}
647}
648
649
650static const char*
651main_window_package_list_view_mode_str(package_list_view_mode mode)
652{
653	if (mode == PROMINENT)
654		return "PROMINENT";
655	return "ALL";
656}
657
658
659static package_list_view_mode
660main_window_str_to_package_list_view_mode(const BString& str)
661{
662	if (str == "PROMINENT")
663		return PROMINENT;
664	return ALL;
665}
666
667
668void
669MainWindow::StoreSettings(BMessage& settings)
670{
671	settings.AddRect(_WindowFrameName(), Frame());
672	if (!fSinglePackageMode) {
673		settings.AddRect("window frame", Frame());
674
675		BMessage columnSettings;
676		if (fPackageListView != NULL)
677			fPackageListView->SaveState(&columnSettings);
678
679		settings.AddMessage("column settings", &columnSettings);
680
681		settings.AddString(SETTING_PACKAGE_LIST_VIEW_MODE,
682			main_window_package_list_view_mode_str(
683				fModel.PackageListViewMode()));
684		settings.AddBool(SETTING_SHOW_AVAILABLE_PACKAGES,
685			fModel.ShowAvailablePackages());
686		settings.AddBool(SETTING_SHOW_INSTALLED_PACKAGES,
687			fModel.ShowInstalledPackages());
688		settings.AddBool(SETTING_SHOW_DEVELOP_PACKAGES,
689			fModel.ShowDevelopPackages());
690		settings.AddBool(SETTING_SHOW_SOURCE_PACKAGES,
691			fModel.ShowSourcePackages());
692		settings.AddBool(SETTING_CAN_SHARE_ANONYMOUS_USER_DATA,
693			fModel.CanShareAnonymousUsageData());
694	}
695
696	settings.AddString("username", fModel.Nickname());
697}
698
699
700void
701MainWindow::Consume(ProcessCoordinator *item)
702{
703	_AddProcessCoordinator(item);
704}
705
706
707void
708MainWindow::PackageChanged(const PackageInfoEvent& event)
709{
710	uint32 watchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE;
711	if ((event.Changes() & watchedChanges) != 0) {
712		PackageInfoRef ref(event.Package());
713		BMessage message(MSG_PACKAGE_CHANGED);
714		message.AddPointer("package", ref.Get());
715		ref.Detach();
716			// reference needs to be released by MessageReceived();
717		PostMessage(&message);
718	}
719}
720
721
722void
723MainWindow::_BuildMenu(BMenuBar* menuBar)
724{
725	BMenu* menu = new BMenu(B_TRANSLATE_SYSTEM_NAME("HaikuDepot"));
726	fRefreshRepositoriesItem = new BMenuItem(
727		B_TRANSLATE("Refresh repositories"), new BMessage(MSG_REFRESH_REPOS));
728	menu->AddItem(fRefreshRepositoriesItem);
729	menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories"
730		B_UTF8_ELLIPSIS), new BMessage(MSG_MANAGE_REPOS)));
731	menu->AddItem(new BMenuItem(B_TRANSLATE("Check for updates"
732		B_UTF8_ELLIPSIS), new BMessage(MSG_SOFTWARE_UPDATER)));
733	menu->AddSeparatorItem();
734	menu->AddItem(new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS),
735		new BMessage(MSG_SETTINGS), ','));
736	menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
737		new BMessage(B_QUIT_REQUESTED), 'Q'));
738	menuBar->AddItem(menu);
739
740	fRepositoryMenu = new BMenu(B_TRANSLATE("Repositories"));
741	menuBar->AddItem(fRepositoryMenu);
742
743	menu = new BMenu(B_TRANSLATE("Show"));
744
745	fShowAvailablePackagesItem = new BMenuItem(
746		B_TRANSLATE("Available packages"),
747		new BMessage(MSG_SHOW_AVAILABLE_PACKAGES));
748	menu->AddItem(fShowAvailablePackagesItem);
749
750	fShowInstalledPackagesItem = new BMenuItem(
751		B_TRANSLATE("Installed packages"),
752		new BMessage(MSG_SHOW_INSTALLED_PACKAGES));
753	menu->AddItem(fShowInstalledPackagesItem);
754
755	menu->AddSeparatorItem();
756
757	fShowDevelopPackagesItem = new BMenuItem(
758		B_TRANSLATE("Develop packages"),
759		new BMessage(MSG_SHOW_DEVELOP_PACKAGES));
760	menu->AddItem(fShowDevelopPackagesItem);
761
762	fShowSourcePackagesItem = new BMenuItem(
763		B_TRANSLATE("Source packages"),
764		new BMessage(MSG_SHOW_SOURCE_PACKAGES));
765	menu->AddItem(fShowSourcePackagesItem);
766
767	menuBar->AddItem(menu);
768}
769
770
771void
772MainWindow::_BuildUserMenu(BMenuBar* menuBar)
773{
774	fUserMenu = new BMenu(B_TRANSLATE("Not logged in"));
775
776	fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS),
777		new BMessage(MSG_LOG_IN));
778	fUserMenu->AddItem(fLogInItem);
779
780	fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"),
781		new BMessage(MSG_LOG_OUT));
782	fUserMenu->AddItem(fLogOutItem);
783
784	BMenuItem *latestUserUsageConditionsMenuItem =
785		new BMenuItem(B_TRANSLATE("View latest usage conditions"
786			B_UTF8_ELLIPSIS),
787			new BMessage(MSG_VIEW_LATEST_USER_USAGE_CONDITIONS));
788	fUserMenu->AddItem(latestUserUsageConditionsMenuItem);
789
790	fUsersUserUsageConditionsMenuItem =
791		new BMenuItem(B_TRANSLATE("View agreed usage conditions"
792			B_UTF8_ELLIPSIS),
793			new BMessage(MSG_VIEW_USERS_USER_USAGE_CONDITIONS));
794	fUserMenu->AddItem(fUsersUserUsageConditionsMenuItem);
795
796	menuBar->AddItem(fUserMenu);
797}
798
799
800void
801MainWindow::_RestoreNickname(const BMessage& settings)
802{
803	BString nickname;
804	if (settings.FindString("username", &nickname) == B_OK
805		&& nickname.Length() > 0) {
806		fModel.SetNickname(nickname);
807	}
808}
809
810
811const char*
812MainWindow::_WindowFrameName() const
813{
814	if (fSinglePackageMode)
815		return "small window frame";
816
817	return "window frame";
818}
819
820
821void
822MainWindow::_RestoreWindowFrame(const BMessage& settings)
823{
824	BRect frame = Frame();
825
826	BRect windowFrame;
827	bool fromSettings = false;
828	if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) {
829		frame = windowFrame;
830		fromSettings = true;
831	} else if (!fSinglePackageMode) {
832		// Resize to occupy a certain screen size
833		BRect screenFrame = BScreen(this).Frame();
834		float width = frame.Width();
835		float height = frame.Height();
836		if (width < screenFrame.Width() * .666f
837			&& height < screenFrame.Height() * .666f) {
838			frame.bottom = frame.top + screenFrame.Height() * .666f;
839			frame.right = frame.left
840				+ std::min(screenFrame.Width() * .666f, height * 7 / 5);
841		}
842	}
843
844	MoveTo(frame.LeftTop());
845	ResizeTo(frame.Width(), frame.Height());
846
847	if (fromSettings)
848		MoveOnScreen();
849	else
850		CenterOnScreen();
851}
852
853
854void
855MainWindow::_RestoreModelSettings(const BMessage& settings)
856{
857	BString packageListViewMode;
858	if (settings.FindString(SETTING_PACKAGE_LIST_VIEW_MODE,
859			&packageListViewMode) == B_OK) {
860		fModel.SetPackageListViewMode(
861			main_window_str_to_package_list_view_mode(packageListViewMode));
862	}
863
864	bool showOption;
865	if (settings.FindBool(SETTING_SHOW_AVAILABLE_PACKAGES, &showOption) == B_OK)
866		fModel.SetShowAvailablePackages(showOption);
867	if (settings.FindBool(SETTING_SHOW_INSTALLED_PACKAGES, &showOption) == B_OK)
868		fModel.SetShowInstalledPackages(showOption);
869	if (settings.FindBool(SETTING_SHOW_DEVELOP_PACKAGES, &showOption) == B_OK)
870		fModel.SetShowDevelopPackages(showOption);
871	if (settings.FindBool(SETTING_SHOW_SOURCE_PACKAGES, &showOption) == B_OK)
872		fModel.SetShowSourcePackages(showOption);
873	if (settings.FindBool(SETTING_CAN_SHARE_ANONYMOUS_USER_DATA,
874			&showOption) == B_OK) {
875		fModel.SetCanShareAnonymousUsageData(showOption);
876	}
877}
878
879
880void
881MainWindow::_MaybePromptCanShareAnonymousUserData(const BMessage& settings)
882{
883	bool showOption;
884	if (settings.FindBool(SETTING_CAN_SHARE_ANONYMOUS_USER_DATA,
885			&showOption) == B_NAME_NOT_FOUND) {
886		_PromptCanShareAnonymousUserData();
887	}
888}
889
890
891void
892MainWindow::_PromptCanShareAnonymousUserData()
893{
894	BAlert* alert = new(std::nothrow) BAlert(
895		B_TRANSLATE("Sending anonymous usage data"),
896		B_TRANSLATE("Would it be acceptable to send anonymous usage data to the"
897			" HaikuDepotServer system from this computer? You can change your"
898			" preference in the \"Settings\" window later."),
899		B_TRANSLATE("No"),
900		B_TRANSLATE("Yes"));
901
902	int32 result = alert->Go();
903	fModel.SetCanShareAnonymousUsageData(1 == result);
904}
905
906
907void
908MainWindow::_InitWorkerThreads()
909{
910	fPackageToPopulateSem = create_sem(0, "PopulatePackage");
911	if (fPackageToPopulateSem >= 0) {
912		fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker,
913			"Package Populator", B_NORMAL_PRIORITY, this);
914		if (fPopulatePackageWorker >= 0)
915			resume_thread(fPopulatePackageWorker);
916	} else
917		fPopulatePackageWorker = -1;
918}
919
920
921void
922MainWindow::_AdoptModelControls()
923{
924	if (fSinglePackageMode)
925		return;
926
927	BAutolock locker(fModel.Lock());
928	fShowAvailablePackagesItem->SetMarked(fModel.ShowAvailablePackages());
929	fShowInstalledPackagesItem->SetMarked(fModel.ShowInstalledPackages());
930	fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages());
931	fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages());
932
933	if (fModel.PackageListViewMode() == PROMINENT)
934		fListTabs->Select(TAB_PROMINENT_PACKAGES);
935	else
936		fListTabs->Select(TAB_ALL_PACKAGES);
937
938	fFilterView->AdoptModel(fModel);
939}
940
941
942void
943MainWindow::_AdoptModel()
944{
945	HDTRACE("adopting model to main window ui");
946
947	if (fSinglePackageMode)
948		return;
949
950	std::vector<DepotInfoRef> depots = _CreateSnapshotOfDepots();
951	std::vector<DepotInfoRef>::iterator it;
952
953	fFeaturedPackagesView->BeginAddRemove();
954
955	for (it = depots.begin(); it != depots.end(); it++) {
956		DepotInfoRef depotInfoRef = *it;
957		for (int i = 0; i < depotInfoRef->CountPackages(); i++) {
958			PackageInfoRef package = depotInfoRef->PackageAtIndex(i);
959			_AddRemovePackageFromLists(package);
960		}
961	}
962
963	fFeaturedPackagesView->EndAddRemove();
964
965	_AdoptModelControls();
966}
967
968
969void
970MainWindow::_AddRemovePackageFromLists(const PackageInfoRef& package)
971{
972	bool matches;
973
974	{
975		AutoLocker<BLocker> modelLocker(fModel.Lock());
976		matches = fModel.MatchesFilter(package);
977	}
978
979	if (matches) {
980		if (package->IsProminent())
981			fFeaturedPackagesView->AddPackage(package);
982		fPackageListView->AddPackage(package);
983	} else {
984		fFeaturedPackagesView->RemovePackage(package);
985		fPackageListView->RemovePackage(package);
986	}
987}
988
989
990void
991MainWindow::_SetupDelayedIncrementViewCounter(const PackageInfoRef package) {
992	if (fIncrementViewCounterDelayedRunner != NULL) {
993		fIncrementViewCounterDelayedRunner->SetCount(0);
994		delete fIncrementViewCounterDelayedRunner;
995	}
996	BMessage message(MSG_INCREMENT_VIEW_COUNTER);
997	message.SetString("name", package->Name());
998	fIncrementViewCounterDelayedRunner =
999		new BMessageRunner(BMessenger(this), &message,
1000			kIncrementViewCounterDelayMicros, 1);
1001	if (fIncrementViewCounterDelayedRunner->InitCheck()
1002			!= B_OK) {
1003		HDERROR("unable to init the increment view counter");
1004	}
1005}
1006
1007
1008void
1009MainWindow::_HandleIncrementViewCounter(const BMessage* message)
1010{
1011	BString name;
1012	if (message->FindString("name", &name) == B_OK) {
1013		const PackageInfoRef& viewedPackage =
1014			fPackageInfoView->Package();
1015		if (viewedPackage.IsSet()) {
1016			if (viewedPackage->Name() == name)
1017				_IncrementViewCounter(viewedPackage);
1018			else
1019				HDINFO("incr. view counter; name mismatch");
1020		} else
1021			HDINFO("incr. view counter; no viewed pkg");
1022	} else
1023		HDERROR("incr. view counter; no name");
1024	fIncrementViewCounterDelayedRunner->SetCount(0);
1025	delete fIncrementViewCounterDelayedRunner;
1026	fIncrementViewCounterDelayedRunner = NULL;
1027}
1028
1029
1030void
1031MainWindow::_IncrementViewCounter(const PackageInfoRef package)
1032{
1033	bool shouldIncrementViewCounter = false;
1034
1035	{
1036		AutoLocker<BLocker> modelLocker(fModel.Lock());
1037		bool canShareAnonymousUsageData = fModel.CanShareAnonymousUsageData();
1038		if (canShareAnonymousUsageData && !package->Viewed()) {
1039			package->SetViewed();
1040			shouldIncrementViewCounter = true;
1041		}
1042	}
1043
1044	if (shouldIncrementViewCounter) {
1045		ProcessCoordinator* incrementViewCoordinator =
1046			ProcessCoordinatorFactory::CreateIncrementViewCounter(
1047				&fModel, package);
1048		_AddProcessCoordinator(incrementViewCoordinator);
1049	}
1050}
1051
1052
1053void
1054MainWindow::_AdoptPackage(const PackageInfoRef& package)
1055{
1056	{
1057		BAutolock locker(fModel.Lock());
1058		fPackageInfoView->SetPackage(package);
1059
1060		if (fFeaturedPackagesView != NULL)
1061			fFeaturedPackagesView->SelectPackage(package);
1062		if (fPackageListView != NULL)
1063			fPackageListView->SelectPackage(package);
1064	}
1065
1066	_PopulatePackageAsync(false);
1067}
1068
1069
1070void
1071MainWindow::_ClearPackage()
1072{
1073	fPackageInfoView->Clear();
1074}
1075
1076
1077void
1078MainWindow::_StartBulkLoad(bool force)
1079{
1080	if (fFeaturedPackagesView != NULL)
1081		fFeaturedPackagesView->Clear();
1082	if (fPackageListView != NULL)
1083		fPackageListView->Clear();
1084	fPackageInfoView->Clear();
1085
1086	fRefreshRepositoriesItem->SetEnabled(false);
1087	ProcessCoordinator* bulkLoadCoordinator =
1088		ProcessCoordinatorFactory::CreateBulkLoadCoordinator(
1089			fPackageInfoListener, &fModel, force);
1090	_AddProcessCoordinator(bulkLoadCoordinator);
1091}
1092
1093
1094void
1095MainWindow::_BulkLoadCompleteReceived(status_t errorStatus)
1096{
1097	if (errorStatus != B_OK) {
1098		AppUtils::NotifySimpleError(
1099			B_TRANSLATE("Package update error"),
1100			B_TRANSLATE("While updating package data, a problem has arisen "
1101				"that may cause data to be outdated or missing from the "
1102				"application's display. Additional details regarding this "
1103				"problem may be able to be obtained from the application "
1104				"logs."
1105				ALERT_MSG_LOGS_USER_GUIDE));
1106	}
1107
1108	fRefreshRepositoriesItem->SetEnabled(true);
1109	_AdoptModel();
1110	_UpdateAvailableRepositories();
1111
1112	// if after loading everything in, it transpires that there are no
1113	// featured packages then the featured packages should be disabled
1114	// and the user should be switched to the "all packages" view so
1115	// that they are not presented with a blank window!
1116
1117	bool hasProminentPackages = fModel.HasAnyProminentPackages();
1118	fListTabs->TabAt(TAB_PROMINENT_PACKAGES)->SetEnabled(hasProminentPackages);
1119	if (!hasProminentPackages
1120			&& fListTabs->Selection() == TAB_PROMINENT_PACKAGES) {
1121		fModel.SetPackageListViewMode(ALL);
1122		fListTabs->Select(TAB_ALL_PACKAGES);
1123	}
1124}
1125
1126
1127void
1128MainWindow::_NotifyWorkStatusClear()
1129{
1130	BMessage message(MSG_WORK_STATUS_CLEAR);
1131	this->PostMessage(&message, this);
1132}
1133
1134
1135void
1136MainWindow::_HandleWorkStatusClear()
1137{
1138	fWorkStatusView->SetText("");
1139	fWorkStatusView->SetIdle();
1140}
1141
1142
1143/*! Sends off a message to the Window so that it can change the status view
1144    on the front-end in the UI thread.
1145*/
1146
1147void
1148MainWindow::_NotifyWorkStatusChange(const BString& text, float progress)
1149{
1150	BMessage message(MSG_WORK_STATUS_CHANGE);
1151
1152	if (!text.IsEmpty())
1153		message.AddString(KEY_WORK_STATUS_TEXT, text);
1154	message.AddFloat(KEY_WORK_STATUS_PROGRESS, progress);
1155
1156	this->PostMessage(&message, this);
1157}
1158
1159
1160void
1161MainWindow::_HandleExternalPackageUpdateMessageReceived(const BMessage* message)
1162{
1163	BStringList addedPackageNames;
1164	BStringList removedPackageNames;
1165
1166	if (message->FindStrings("added package names",
1167			&addedPackageNames) == B_OK) {
1168		addedPackageNames.Sort();
1169		AutoLocker<BLocker> locker(fModel.Lock());
1170		fModel.SetStateForPackagesByName(addedPackageNames, ACTIVATED);
1171	}
1172	else
1173		HDINFO("no [added package names] key in inbound message");
1174
1175	if (message->FindStrings("removed package names",
1176			&removedPackageNames) == B_OK) {
1177		removedPackageNames.Sort();
1178		AutoLocker<BLocker> locker(fModel.Lock());
1179		fModel.SetStateForPackagesByName(addedPackageNames, UNINSTALLED);
1180	} else
1181		HDINFO("no [removed package names] key in inbound message");
1182}
1183
1184
1185void
1186MainWindow::_HandleWorkStatusChangeMessageReceived(const BMessage* message)
1187{
1188	if (fWorkStatusView == NULL)
1189		return;
1190
1191	BString text;
1192	float progress;
1193
1194	if (message->FindString(KEY_WORK_STATUS_TEXT, &text) == B_OK)
1195		fWorkStatusView->SetText(text);
1196
1197	if (message->FindFloat(KEY_WORK_STATUS_PROGRESS, &progress) == B_OK) {
1198		if (progress < 0.0f)
1199			fWorkStatusView->SetBusy();
1200		else
1201			fWorkStatusView->SetProgress(progress);
1202	} else {
1203		HDERROR("work status change missing progress on update message");
1204		fWorkStatusView->SetProgress(0.0f);
1205	}
1206}
1207
1208
1209/*! This method will cause the package to have its data refreshed from
1210    the server application.  The refresh happens in the background; this method
1211    is asynchronous.
1212*/
1213
1214void
1215MainWindow::_PopulatePackageAsync(bool forcePopulate)
1216{
1217		// Trigger asynchronous package population from the web-app
1218	{
1219		AutoLocker<BLocker> lock(&fPackageToPopulateLock);
1220		fPackageToPopulate = fPackageInfoView->Package();
1221		fForcePopulatePackage = forcePopulate;
1222	}
1223	release_sem_etc(fPackageToPopulateSem, 1, 0);
1224
1225	HDDEBUG("pkg [%s] will be updated from the server.",
1226		fPackageToPopulate->Name().String());
1227}
1228
1229
1230/*! This method will run in the background.  The thread will block until there
1231    is a package to be updated.  When the thread unblocks, it will update the
1232    package with information from the server.
1233*/
1234
1235status_t
1236MainWindow::_PopulatePackageWorker(void* arg)
1237{
1238	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1239
1240	while (acquire_sem(window->fPackageToPopulateSem) == B_OK) {
1241		PackageInfoRef package;
1242		bool force;
1243		{
1244			AutoLocker<BLocker> lock(&window->fPackageToPopulateLock);
1245			package = window->fPackageToPopulate;
1246			force = window->fForcePopulatePackage;
1247		}
1248
1249		if (package.IsSet()) {
1250			uint32 populateFlags = Model::POPULATE_USER_RATINGS
1251				| Model::POPULATE_CHANGELOG;
1252
1253			if (force)
1254				populateFlags |= Model::POPULATE_FORCE;
1255
1256			window->fModel.PopulatePackage(package, populateFlags);
1257
1258			HDDEBUG("populating package [%s]", package->Name().String());
1259		}
1260	}
1261
1262	return 0;
1263}
1264
1265
1266void
1267MainWindow::_OpenSettingsWindow()
1268{
1269	SettingsWindow* window = new SettingsWindow(this, &fModel);
1270	window->Show();
1271}
1272
1273
1274void
1275MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage)
1276{
1277	UserLoginWindow* window = new UserLoginWindow(this,
1278		BRect(0, 0, 500, 400), fModel);
1279
1280	if (onSuccessMessage.what != 0)
1281		window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage);
1282
1283	window->Show();
1284}
1285
1286
1287void
1288MainWindow::_StartUserVerify()
1289{
1290	if (!fModel.Nickname().IsEmpty()) {
1291		_AddProcessCoordinator(
1292			ProcessCoordinatorFactory::CreateUserDetailVerifierCoordinator(
1293				this,
1294					// UserDetailVerifierListener
1295				&fModel) );
1296	}
1297}
1298
1299
1300void
1301MainWindow::_UpdateAuthorization()
1302{
1303	BString nickname(fModel.Nickname());
1304	bool hasUser = !nickname.IsEmpty();
1305
1306	if (fLogOutItem != NULL)
1307		fLogOutItem->SetEnabled(hasUser);
1308	if (fUsersUserUsageConditionsMenuItem != NULL)
1309		fUsersUserUsageConditionsMenuItem->SetEnabled(hasUser);
1310	if (fLogInItem != NULL) {
1311		if (hasUser)
1312			fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS));
1313		else
1314			fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS));
1315	}
1316
1317	if (fUserMenu != NULL) {
1318		BString label;
1319		if (hasUser) {
1320			label = B_TRANSLATE("Logged in as %User%");
1321			label.ReplaceAll("%User%", nickname);
1322		} else {
1323			label = B_TRANSLATE("Not logged in");
1324		}
1325		fUserMenu->Superitem()->SetLabel(label);
1326	}
1327}
1328
1329
1330void
1331MainWindow::_UpdateAvailableRepositories()
1332{
1333	fRepositoryMenu->RemoveItems(0, fRepositoryMenu->CountItems(), true);
1334
1335	fRepositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All repositories"),
1336		new BMessage(MSG_DEPOT_SELECTED)));
1337
1338	fRepositoryMenu->AddItem(new BSeparatorItem());
1339
1340	bool foundSelectedDepot = false;
1341	std::vector<DepotInfoRef> depots = _CreateSnapshotOfDepots();
1342	std::vector<DepotInfoRef>::iterator it;
1343
1344	for (it = depots.begin(); it != depots.end(); it++) {
1345		DepotInfoRef depot = *it;
1346
1347		if (depot->Name().Length() != 0) {
1348			BMessage* message = new BMessage(MSG_DEPOT_SELECTED);
1349			message->AddString("name", depot->Name());
1350			BMenuItem* item = new(std::nothrow) BMenuItem(depot->Name(), message);
1351
1352			if (item == NULL)
1353				HDFATAL("memory exhaustion");
1354
1355			fRepositoryMenu->AddItem(item);
1356
1357			if (depot->Name() == fModel.Depot()) {
1358				item->SetMarked(true);
1359				foundSelectedDepot = true;
1360			}
1361		}
1362	}
1363
1364	if (!foundSelectedDepot)
1365		fRepositoryMenu->ItemAt(0)->SetMarked(true);
1366}
1367
1368
1369bool
1370MainWindow::_SelectedPackageHasWebAppRepositoryCode()
1371{
1372	const PackageInfoRef& package = fPackageInfoView->Package();
1373	const BString depotName = package->DepotName();
1374
1375	if (depotName.IsEmpty()) {
1376		HDDEBUG("the package [%s] has no depot name", package->Name().String());
1377	} else {
1378		const DepotInfo* depot = fModel.DepotForName(depotName);
1379
1380		if (depot == NULL) {
1381			HDINFO("the depot [%s] was not able to be found",
1382				depotName.String());
1383		} else {
1384			BString repositoryCode = depot->WebAppRepositoryCode();
1385
1386			if (repositoryCode.IsEmpty()) {
1387				HDINFO("the depot [%s] has no web app repository code",
1388					depotName.String());
1389			} else
1390				return true;
1391		}
1392	}
1393
1394	return false;
1395}
1396
1397
1398void
1399MainWindow::_RatePackage()
1400{
1401	if (!_SelectedPackageHasWebAppRepositoryCode()) {
1402		BAlert* alert = new(std::nothrow) BAlert(
1403			B_TRANSLATE("Rating not possible"),
1404			B_TRANSLATE("This package doesn't seem to be on the HaikuDepot "
1405				"Server, so it's not possible to create a new rating "
1406				"or edit an existing rating."),
1407			B_TRANSLATE("OK"));
1408		alert->Go();
1409    	return;
1410	}
1411
1412	if (fModel.Nickname().IsEmpty()) {
1413		BAlert* alert = new(std::nothrow) BAlert(
1414			B_TRANSLATE("Not logged in"),
1415			B_TRANSLATE("You need to be logged into an account before you "
1416				"can rate packages."),
1417			B_TRANSLATE("Cancel"),
1418			B_TRANSLATE("Login or Create account"));
1419
1420		if (alert == NULL)
1421			return;
1422
1423		int32 choice = alert->Go();
1424		if (choice == 1)
1425			_OpenLoginWindow(BMessage(MSG_RATE_PACKAGE));
1426		return;
1427	}
1428
1429	// TODO: Allow only one RatePackageWindow
1430	// TODO: Mechanism for remembering the window frame
1431	RatePackageWindow* window = new RatePackageWindow(this,
1432		BRect(0, 0, 500, 400), fModel);
1433	window->SetPackage(fPackageInfoView->Package());
1434	window->Show();
1435}
1436
1437
1438void
1439MainWindow::_ShowScreenshot()
1440{
1441	// TODO: Mechanism for remembering the window frame
1442	if (fScreenshotWindow == NULL)
1443		fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400), &fModel);
1444
1445	if (fScreenshotWindow->LockWithTimeout(1000) != B_OK)
1446		return;
1447
1448	fScreenshotWindow->SetPackage(fPackageInfoView->Package());
1449
1450	if (fScreenshotWindow->IsHidden())
1451		fScreenshotWindow->Show();
1452	else
1453		fScreenshotWindow->Activate();
1454
1455	fScreenshotWindow->Unlock();
1456}
1457
1458
1459void
1460MainWindow::_ViewUserUsageConditions(
1461	UserUsageConditionsSelectionMode mode)
1462{
1463	UserUsageConditionsWindow* window = new UserUsageConditionsWindow(
1464		fModel, mode);
1465	window->Show();
1466}
1467
1468
1469void
1470MainWindow::UserCredentialsFailed()
1471{
1472	BString message = B_TRANSLATE("The password previously "
1473		"supplied for the user [%Nickname%] is not currently "
1474		"valid. The user will be logged-out of this application "
1475		"and you should login again with your updated password.");
1476	message.ReplaceAll("%Nickname%", fModel.Nickname());
1477
1478	AppUtils::NotifySimpleError(B_TRANSLATE("Login issue"),
1479		message);
1480
1481	{
1482		AutoLocker<BLocker> locker(fModel.Lock());
1483		fModel.SetNickname("");
1484	}
1485}
1486
1487
1488/*! \brief This method is invoked from the UserDetailVerifierProcess on a
1489		   background thread.  For this reason it lodges a message into itself
1490		   which can then be handled on the main thread.
1491*/
1492
1493void
1494MainWindow::UserUsageConditionsNotLatest(const UserDetail& userDetail)
1495{
1496	BMessage message(MSG_USER_USAGE_CONDITIONS_NOT_LATEST);
1497	BMessage detailsMessage;
1498	if (userDetail.Archive(&detailsMessage, true) != B_OK
1499			|| message.AddMessage("userDetail", &detailsMessage) != B_OK) {
1500		HDERROR("unable to archive the user detail into a message");
1501	}
1502	else
1503		BMessenger(this).SendMessage(&message);
1504}
1505
1506
1507void
1508MainWindow::_HandleUserUsageConditionsNotLatest(
1509	const UserDetail& userDetail)
1510{
1511	ToLatestUserUsageConditionsWindow* window =
1512		new ToLatestUserUsageConditionsWindow(this, fModel, userDetail);
1513	window->Show();
1514}
1515
1516
1517void
1518MainWindow::_AddProcessCoordinator(ProcessCoordinator* item)
1519{
1520	AutoLocker<BLocker> lock(&fCoordinatorLock);
1521
1522	if (fShouldCloseWhenNoProcessesToCoordinate) {
1523		HDINFO("system shutting down --> new process coordinator [%s] rejected",
1524			item->Name().String());
1525		return;
1526	}
1527
1528	item->SetListener(this);
1529
1530	if (fCoordinator == NULL) {
1531		if (acquire_sem(fCoordinatorRunningSem) != B_OK)
1532			debugger("unable to acquire the process coordinator sem");
1533		HDINFO("adding and starting a process coordinator [%s]",
1534			item->Name().String());
1535		delete fCoordinator;
1536		fCoordinator = item;
1537		fCoordinator->Start();
1538	} else {
1539		HDINFO("adding process coordinator [%s] to the queue",
1540			item->Name().String());
1541		fCoordinatorQueue.push(item);
1542	}
1543}
1544
1545
1546void
1547MainWindow::_SpinUntilProcessCoordinatorComplete()
1548{
1549	while (true) {
1550		if (acquire_sem(fCoordinatorRunningSem) != B_OK)
1551			debugger("unable to acquire the process coordinator sem");
1552		if (release_sem(fCoordinatorRunningSem) != B_OK)
1553			debugger("unable to release the process coordinator sem");
1554		{
1555			AutoLocker<BLocker> lock(&fCoordinatorLock);
1556			if (fCoordinator == NULL)
1557				return;
1558		}
1559	}
1560}
1561
1562
1563void
1564MainWindow::_StopProcessCoordinators()
1565{
1566	HDINFO("will stop all queued process coordinators");
1567	AutoLocker<BLocker> lock(&fCoordinatorLock);
1568
1569	while (!fCoordinatorQueue.empty()) {
1570		ProcessCoordinator* processCoordinator
1571			= fCoordinatorQueue.front();
1572		HDINFO("will drop queued process coordinator [%s]",
1573			processCoordinator->Name().String());
1574		fCoordinatorQueue.pop();
1575		delete processCoordinator;
1576	}
1577
1578	if (fCoordinator != NULL)
1579		fCoordinator->RequestStop();
1580}
1581
1582
1583/*! This method is called when there is some change in the bulk load process
1584	or other process coordinator.
1585	A change may mean that a new process has started / stopped etc... or it
1586	may mean that the entire coordinator has finished.
1587*/
1588
1589void
1590MainWindow::CoordinatorChanged(ProcessCoordinatorState& coordinatorState)
1591{
1592	BMessage message(MSG_PROCESS_COORDINATOR_CHANGED);
1593	if (coordinatorState.Archive(&message, true) != B_OK) {
1594		HDFATAL("unable to archive message when the process coordinator"
1595			" has changed");
1596	}
1597	BMessenger(this).SendMessage(&message);
1598}
1599
1600
1601void
1602MainWindow::_HandleProcessCoordinatorChanged(ProcessCoordinatorState& coordinatorState)
1603{
1604	AutoLocker<BLocker> lock(&fCoordinatorLock);
1605
1606	if (fCoordinator->Identifier()
1607			== coordinatorState.ProcessCoordinatorIdentifier()) {
1608		if (!coordinatorState.IsRunning()) {
1609			if (release_sem(fCoordinatorRunningSem) != B_OK)
1610				debugger("unable to release the process coordinator sem");
1611			HDINFO("process coordinator [%s] did complete",
1612				fCoordinator->Name().String());
1613			// complete the last one that just finished
1614			BMessage* message = fCoordinator->Message();
1615
1616			if (message != NULL) {
1617				BMessenger messenger(this);
1618				message->AddInt64(KEY_ERROR_STATUS,
1619					(int64) fCoordinator->ErrorStatus());
1620				messenger.SendMessage(message);
1621			}
1622
1623			HDDEBUG("process coordinator report;\n---\n%s\n----",
1624				fCoordinator->LogReport().String());
1625
1626			delete fCoordinator;
1627			fCoordinator = NULL;
1628
1629			// now schedule the next one.
1630			if (!fCoordinatorQueue.empty()) {
1631				if (acquire_sem(fCoordinatorRunningSem) != B_OK)
1632					debugger("unable to acquire the process coordinator sem");
1633				fCoordinator = fCoordinatorQueue.front();
1634				HDINFO("starting next process coordinator [%s]",
1635					fCoordinator->Name().String());
1636				fCoordinatorQueue.pop();
1637				fCoordinator->Start();
1638			}
1639			else {
1640				_NotifyWorkStatusClear();
1641				if (fShouldCloseWhenNoProcessesToCoordinate) {
1642					HDINFO("no more processes to coord --> will quit");
1643					BMessage message(B_QUIT_REQUESTED);
1644					PostMessage(&message);
1645				}
1646			}
1647		}
1648		else {
1649			_NotifyWorkStatusChange(coordinatorState.Message(),
1650				coordinatorState.Progress());
1651				// show the progress to the user.
1652		}
1653	} else {
1654		_NotifyWorkStatusClear();
1655		HDINFO("! unknown process coordinator changed");
1656	}
1657}
1658
1659
1660static package_list_view_mode
1661main_window_tab_to_package_list_view_mode(int32 tab)
1662{
1663	if (tab == TAB_PROMINENT_PACKAGES)
1664		return PROMINENT;
1665	return ALL;
1666}
1667
1668
1669void
1670MainWindow::_HandleChangePackageListViewMode()
1671{
1672	package_list_view_mode tabMode = main_window_tab_to_package_list_view_mode(
1673		fListTabs->Selection());
1674	package_list_view_mode modelMode = fModel.PackageListViewMode();
1675
1676	if (tabMode != modelMode) {
1677		BAutolock locker(fModel.Lock());
1678		fModel.SetPackageListViewMode(tabMode);
1679	}
1680}
1681
1682
1683std::vector<DepotInfoRef>
1684MainWindow::_CreateSnapshotOfDepots()
1685{
1686	std::vector<DepotInfoRef> result;
1687	BAutolock locker(fModel.Lock());
1688	int32 countDepots = fModel.CountDepots();
1689	for(int32 i = 0; i < countDepots; i++)
1690		result.push_back(fModel.DepotAtIndex(i));
1691	return result;
1692}
1693
1694
1695/*! This will get invoked in the case that a screenshot has been cached
1696    and so could now be loaded by some UI element. This method will then
1697    signal to other UI elements that they could load a screenshot should
1698    they have been waiting for it.
1699*/
1700
1701void
1702MainWindow::_HandleScreenshotCached(const BMessage* message)
1703{
1704	ScreenshotCoordinate coordinate(message);
1705	fPackageInfoView->HandleScreenshotCached(coordinate);
1706}
1707