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