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