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