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