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