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