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