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