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