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