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