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