xref: /haiku/src/apps/haikudepot/ui/MainWindow.cpp (revision 3995592cdf304335132305e27c40cbb0b1ac46e3)
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, 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 
16 #include <stdio.h>
17 
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 <package/Context.h>
36 #include <package/manager/Exceptions.h>
37 #include <package/manager/RepositoryBuilder.h>
38 #include <package/RefreshRepositoryRequest.h>
39 #include <package/PackageRoster.h>
40 #include "package/RepositoryCache.h"
41 #include <package/solver/SolverPackage.h>
42 #include <package/solver/SolverProblem.h>
43 #include <package/solver/SolverProblemSolution.h>
44 #include <package/solver/SolverRepository.h>
45 #include <package/solver/SolverResult.h>
46 
47 #include "AutoDeleter.h"
48 #include "AutoLocker.h"
49 #include "DecisionProvider.h"
50 #include "FeaturedPackagesView.h"
51 #include "FilterView.h"
52 #include "JobStateListener.h"
53 #include "PackageInfoView.h"
54 #include "PackageListView.h"
55 #include "PackageManager.h"
56 #include "RatePackageWindow.h"
57 #include "support.h"
58 #include "ScreenshotWindow.h"
59 #include "UserLoginWindow.h"
60 #include "WorkStatusView.h"
61 
62 
63 #undef B_TRANSLATION_CONTEXT
64 #define B_TRANSLATION_CONTEXT "MainWindow"
65 
66 
67 enum {
68 	MSG_MODEL_WORKER_DONE		= 'mmwd',
69 	MSG_REFRESH_REPOS			= 'mrrp',
70 	MSG_MANAGE_REPOS			= 'mmrp',
71 	MSG_SOFTWARE_UPDATER		= 'mswu',
72 	MSG_LOG_IN					= 'lgin',
73 	MSG_LOG_OUT					= 'lgot',
74 	MSG_AUTHORIZATION_CHANGED	= 'athc',
75 	MSG_PACKAGE_CHANGED			= 'pchd',
76 
77 	MSG_SHOW_FEATURED_PACKAGES	= 'sofp',
78 	MSG_SHOW_AVAILABLE_PACKAGES	= 'savl',
79 	MSG_SHOW_INSTALLED_PACKAGES	= 'sins',
80 	MSG_SHOW_SOURCE_PACKAGES	= 'ssrc',
81 	MSG_SHOW_DEVELOP_PACKAGES	= 'sdvl'
82 };
83 
84 
85 using namespace BPackageKit;
86 using namespace BPackageKit::BManager::BPrivate;
87 
88 
89 typedef std::map<BString, PackageInfoRef> PackageInfoMap;
90 typedef std::map<BString, DepotInfo> DepotInfoMap;
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 MessageModelListener : public ModelListener {
107 public:
108 	MessageModelListener(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 private:
121 	BMessenger	fMessenger;
122 };
123 
124 
125 MainWindow::MainWindow(const BMessage& settings)
126 	:
127 	BWindow(BRect(50, 50, 650, 550), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
128 		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
129 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
130 	fScreenshotWindow(NULL),
131 	fUserMenu(NULL),
132 	fLogInItem(NULL),
133 	fLogOutItem(NULL),
134 	fModelListener(new MessageModelListener(BMessenger(this)), true),
135 	fBulkLoadStateMachine(&fModel),
136 	fTerminating(false),
137 	fSinglePackageMode(false),
138 	fModelWorker(B_BAD_THREAD_ID)
139 {
140 	BMenuBar* menuBar = new BMenuBar(B_TRANSLATE("Main Menu"));
141 	_BuildMenu(menuBar);
142 
143 	BMenuBar* userMenuBar = new BMenuBar(B_TRANSLATE("User Menu"));
144 	_BuildUserMenu(userMenuBar);
145 	set_small_font(userMenuBar);
146 	userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET,
147 		menuBar->MaxSize().height));
148 
149 	fFilterView = new FilterView();
150 	fFeaturedPackagesView = new FeaturedPackagesView();
151 	fPackageListView = new PackageListView(fModel.Lock());
152 	fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
153 
154 	fSplitView = new BSplitView(B_VERTICAL, 5.0f);
155 
156 	BGroupView* featuredPackagesGroup = new BGroupView(B_VERTICAL);
157 	BStringView* featuredPackagesTitle = new BStringView(
158 		"featured packages title", B_TRANSLATE("Featured packages"));
159 	BFont font(be_bold_font);
160 	font.SetSize(font.Size() * 1.3f);
161 	featuredPackagesTitle->SetFont(&font);
162 	featuredPackagesGroup->SetExplicitMaxSize(
163 		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
164 	BLayoutBuilder::Group<>(featuredPackagesGroup)
165 		.Add(featuredPackagesTitle)
166 		.Add(fFeaturedPackagesView)
167 	;
168 
169 	fWorkStatusView = new WorkStatusView("work status");
170 	fPackageListView->AttachWorkStatusView(fWorkStatusView);
171 
172 	BView* listArea = new BView("list area", 0);
173 	fListLayout = new BCardLayout();
174 	listArea->SetLayout(fListLayout);
175 	listArea->AddChild(featuredPackagesGroup);
176 	listArea->AddChild(fPackageListView);
177 
178 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f)
179 		.AddGroup(B_HORIZONTAL, 0.0f)
180 			.Add(menuBar, 1.0f)
181 			.Add(userMenuBar, 0.0f)
182 		.End()
183 		.Add(fFilterView)
184 		.AddSplit(fSplitView)
185 			.AddGroup(B_VERTICAL)
186 				.Add(listArea)
187 				.SetInsets(
188 					B_USE_DEFAULT_SPACING, 0.0f,
189 					B_USE_DEFAULT_SPACING, 0.0f)
190 			.End()
191 			.Add(fPackageInfoView)
192 		.End()
193 		.Add(fWorkStatusView)
194 	;
195 
196 	fSplitView->SetCollapsible(0, false);
197 	fSplitView->SetCollapsible(1, false);
198 
199 	fModel.AddListener(fModelListener);
200 
201 	// Restore settings
202 	BMessage columnSettings;
203 	if (settings.FindMessage("column settings", &columnSettings) == B_OK)
204 		fPackageListView->LoadState(&columnSettings);
205 
206 	bool showOption;
207 	if (settings.FindBool("show featured packages", &showOption) == B_OK)
208 		fModel.SetShowFeaturedPackages(showOption);
209 	if (settings.FindBool("show available packages", &showOption) == B_OK)
210 		fModel.SetShowAvailablePackages(showOption);
211 	if (settings.FindBool("show installed packages", &showOption) == B_OK)
212 		fModel.SetShowInstalledPackages(showOption);
213 	if (settings.FindBool("show develop packages", &showOption) == B_OK)
214 		fModel.SetShowDevelopPackages(showOption);
215 	if (settings.FindBool("show source packages", &showOption) == B_OK)
216 		fModel.SetShowSourcePackages(showOption);
217 
218 	if (fModel.ShowFeaturedPackages())
219 		fListLayout->SetVisibleItem((int32)0);
220 	else
221 		fListLayout->SetVisibleItem(1);
222 
223 	_RestoreUserName(settings);
224 	_RestoreWindowFrame(settings);
225 
226 	atomic_set(&fPackagesToShowListID, 0);
227 
228 	// start worker threads
229 	BPackageRoster().StartWatching(this,
230 		B_WATCH_PACKAGE_INSTALLATION_LOCATIONS);
231 
232 	_StartRefreshWorker();
233 
234 	_InitWorkerThreads();
235 }
236 
237 
238 MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package)
239 	:
240 	BWindow(BRect(50, 50, 650, 350), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
241 		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
242 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
243 	fWorkStatusView(NULL),
244 	fScreenshotWindow(NULL),
245 	fUserMenu(NULL),
246 	fLogInItem(NULL),
247 	fLogOutItem(NULL),
248 	fModelListener(new MessageModelListener(BMessenger(this)), true),
249 	fBulkLoadStateMachine(&fModel),
250 	fTerminating(false),
251 	fSinglePackageMode(true),
252 	fModelWorker(B_BAD_THREAD_ID)
253 {
254 	fFilterView = new FilterView();
255 	fPackageListView = new PackageListView(fModel.Lock());
256 	fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
257 
258 	BLayoutBuilder::Group<>(this, B_VERTICAL)
259 		.Add(fPackageInfoView)
260 		.SetInsets(0, B_USE_WINDOW_INSETS, 0, 0)
261 	;
262 
263 	fModel.AddListener(fModelListener);
264 
265 	// Restore settings
266 	_RestoreUserName(settings);
267 	_RestoreWindowFrame(settings);
268 
269 	fPackageInfoView->SetPackage(package);
270 
271 	_InitWorkerThreads();
272 }
273 
274 
275 MainWindow::~MainWindow()
276 {
277 	BPackageRoster().StopWatching(this);
278 
279 	fTerminating = true;
280 	if (fModelWorker >= 0)
281 		wait_for_thread(fModelWorker, NULL);
282 
283 	delete_sem(fPendingActionsSem);
284 	if (fPendingActionsWorker >= 0)
285 		wait_for_thread(fPendingActionsWorker, NULL);
286 
287 	delete_sem(fPackageToPopulateSem);
288 	if (fPopulatePackageWorker >= 0)
289 		wait_for_thread(fPopulatePackageWorker, NULL);
290 
291 	delete_sem(fNewPackagesToShowSem);
292 	delete_sem(fShowPackagesAcknowledgeSem);
293 	if (fShowPackagesWorker >= 0)
294 		wait_for_thread(fShowPackagesWorker, NULL);
295 
296 	if (fScreenshotWindow != NULL && fScreenshotWindow->Lock())
297 		fScreenshotWindow->Quit();
298 }
299 
300 
301 bool
302 MainWindow::QuitRequested()
303 {
304 	BMessage settings;
305 	StoreSettings(settings);
306 
307 	BMessage message(MSG_MAIN_WINDOW_CLOSED);
308 	message.AddMessage("window settings", &settings);
309 
310 	be_app->PostMessage(&message);
311 
312 	return true;
313 }
314 
315 
316 void
317 MainWindow::MessageReceived(BMessage* message)
318 {
319 	switch (message->what) {
320 		case MSG_MODEL_WORKER_DONE:
321 		{
322 			fModelWorker = B_BAD_THREAD_ID;
323 			_AdoptModel();
324 			fFilterView->AdoptModel(fModel);
325 			fWorkStatusView->SetIdle();
326 			break;
327 		}
328 		case B_SIMPLE_DATA:
329 		case B_REFS_RECEIVED:
330 			// TODO: ?
331 			break;
332 
333 		case B_PACKAGE_UPDATE:
334 			// TODO: We should do a more selective update depending on the
335 			// "event", "location", and "change count" fields!
336 			_StartRefreshWorker(false);
337 			break;
338 
339 		case MSG_REFRESH_REPOS:
340 			_StartRefreshWorker(true);
341 			break;
342 
343 		case MSG_MANAGE_REPOS:
344 			be_roster->Launch("application/x-vnd.Haiku-Repositories");
345 			break;
346 
347 		case MSG_SOFTWARE_UPDATER:
348 			be_roster->Launch("application/x-vnd.haiku-softwareupdater");
349 			break;
350 
351 		case MSG_LOG_IN:
352 			_OpenLoginWindow(BMessage());
353 			break;
354 
355 		case MSG_LOG_OUT:
356 			fModel.SetUsername("");
357 			break;
358 
359 		case MSG_AUTHORIZATION_CHANGED:
360 			_UpdateAuthorization();
361 			break;
362 
363 		case MSG_SHOW_FEATURED_PACKAGES:
364 			{
365 				BAutolock locker(fModel.Lock());
366 				fModel.SetShowFeaturedPackages(
367 					!fModel.ShowFeaturedPackages());
368 			}
369 			_AdoptModel();
370 			break;
371 
372 		case MSG_SHOW_AVAILABLE_PACKAGES:
373 			{
374 				BAutolock locker(fModel.Lock());
375 				fModel.SetShowAvailablePackages(
376 					!fModel.ShowAvailablePackages());
377 			}
378 			_AdoptModel();
379 			break;
380 
381 		case MSG_SHOW_INSTALLED_PACKAGES:
382 			{
383 				BAutolock locker(fModel.Lock());
384 				fModel.SetShowInstalledPackages(
385 					!fModel.ShowInstalledPackages());
386 			}
387 			_AdoptModel();
388 			break;
389 
390 		case MSG_SHOW_SOURCE_PACKAGES:
391 			{
392 				BAutolock locker(fModel.Lock());
393 				fModel.SetShowSourcePackages(!fModel.ShowSourcePackages());
394 			}
395 			_AdoptModel();
396 			break;
397 
398 		case MSG_SHOW_DEVELOP_PACKAGES:
399 			{
400 				BAutolock locker(fModel.Lock());
401 				fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages());
402 			}
403 			_AdoptModel();
404 			break;
405 
406 		case MSG_PACKAGE_SELECTED:
407 		{
408 			BString name;
409 			if (message->FindString("name", &name) == B_OK) {
410 				BAutolock locker(fModel.Lock());
411 				int count = fVisiblePackages.CountItems();
412 				for (int i = 0; i < count; i++) {
413 					const PackageInfoRef& package
414 						= fVisiblePackages.ItemAtFast(i);
415 					if (package.Get() != NULL && package->Name() == name) {
416 						locker.Unlock();
417 						_AdoptPackage(package);
418 						break;
419 					}
420 				}
421 			} else {
422 				_ClearPackage();
423 			}
424 			break;
425 		}
426 
427 		case MSG_CATEGORY_SELECTED:
428 		{
429 			BString name;
430 			if (message->FindString("name", &name) != B_OK)
431 				name = "";
432 			{
433 				BAutolock locker(fModel.Lock());
434 				fModel.SetCategory(name);
435 			}
436 			_AdoptModel();
437 			break;
438 		}
439 
440 		case MSG_DEPOT_SELECTED:
441 		{
442 			BString name;
443 			if (message->FindString("name", &name) != B_OK)
444 				name = "";
445 			{
446 				BAutolock locker(fModel.Lock());
447 				fModel.SetDepot(name);
448 			}
449 			_AdoptModel();
450 			break;
451 		}
452 
453 		case MSG_SEARCH_TERMS_MODIFIED:
454 		{
455 			// TODO: Do this with a delay!
456 			BString searchTerms;
457 			if (message->FindString("search terms", &searchTerms) != B_OK)
458 				searchTerms = "";
459 			{
460 				BAutolock locker(fModel.Lock());
461 				fModel.SetSearchTerms(searchTerms);
462 			}
463 			_AdoptModel();
464 			break;
465 		}
466 
467 		case MSG_PACKAGE_CHANGED:
468 		{
469 			PackageInfo* info;
470 			if (message->FindPointer("package", (void**)&info) == B_OK) {
471 				PackageInfoRef ref(info, true);
472 				uint32 changes;
473 				if (message->FindUInt32("changes", &changes) != B_OK)
474 					changes = 0;
475 				if ((changes & PKG_CHANGED_STATE) != 0) {
476 					BAutolock locker(fModel.Lock());
477 					fModel.SetPackageState(ref, ref->State());
478 				}
479 
480 				// Asynchronous updates to the package information
481 				// can mean that the package needs to be added or
482 				// removed to/from the visible packages when the current
483 				// filter parameters are re-evaluated on this package.
484 				bool wasVisible = fVisiblePackages.Contains(ref);
485 				bool isVisible;
486 				{
487 					BAutolock locker(fModel.Lock());
488 					// The package didn't get a chance yet to be in the
489 					// visible package list
490 					isVisible = fModel.MatchesFilter(ref);
491 
492 					// Transfer this single package, otherwise we miss
493 					// other packages if they appear or disappear along
494 					// with this one before receive a notification for
495 					// them.
496 					if (isVisible) {
497 						fVisiblePackages.Add(ref);
498 					} else if (wasVisible)
499 						fVisiblePackages.Remove(ref);
500 				}
501 
502 				if (wasVisible != isVisible) {
503 					if (!isVisible) {
504 						fPackageListView->RemovePackage(ref);
505 						fFeaturedPackagesView->RemovePackage(ref);
506 					} else {
507 						fPackageListView->AddPackage(ref);
508 						if (ref->IsProminent())
509 							fFeaturedPackagesView->AddPackage(ref);
510 					}
511 				}
512 
513 				if (!fSinglePackageMode && (changes & PKG_CHANGED_STATE) != 0)
514 					fWorkStatusView->PackageStatusChanged(ref);
515 			}
516 			break;
517 		}
518 
519 		case MSG_RATE_PACKAGE:
520 			_RatePackage();
521 			break;
522 
523 		case MSG_SHOW_SCREENSHOT:
524 			_ShowScreenshot();
525 			break;
526 
527 		case MSG_PACKAGE_WORKER_BUSY:
528 		{
529 			BString reason;
530 			status_t status = message->FindString("reason", &reason);
531 			if (status != B_OK)
532 				break;
533 			if (!fSinglePackageMode)
534 				fWorkStatusView->SetBusy(reason);
535 			break;
536 		}
537 
538 		case MSG_PACKAGE_WORKER_IDLE:
539 			if (!fSinglePackageMode)
540 				fWorkStatusView->SetIdle();
541 			break;
542 
543 		case MSG_ADD_VISIBLE_PACKAGES:
544 		{
545 			struct SemaphoreReleaser {
546 				SemaphoreReleaser(sem_id semaphore)
547 					:
548 					fSemaphore(semaphore)
549 				{ }
550 
551 				~SemaphoreReleaser() { release_sem(fSemaphore); }
552 
553 				sem_id fSemaphore;
554 			};
555 
556 			// Make sure acknowledge semaphore is released even on error,
557 			// so the worker thread won't be blocked
558 			SemaphoreReleaser acknowledger(fShowPackagesAcknowledgeSem);
559 
560 			int32 numPackages = 0;
561 			type_code unused;
562 			status_t status = message->GetInfo("package_ref", &unused,
563 				&numPackages);
564 			if (status != B_OK || numPackages == 0)
565 				break;
566 
567 			int32 listID = 0;
568 			status = message->FindInt32("list_id", &listID);
569 			if (status != B_OK)
570 				break;
571 			if (listID != atomic_get(&fPackagesToShowListID)) {
572 				// list is outdated, ignore
573 				break;
574 			}
575 
576 			for (int i = 0; i < numPackages; i++) {
577 				PackageInfo* packageRaw = NULL;
578 				status = message->FindPointer("package_ref", i,
579 					(void**)&packageRaw);
580 				if (status != B_OK)
581 					break;
582 				PackageInfoRef package(packageRaw, true);
583 
584 				fPackageListView->AddPackage(package);
585 				if (package->IsProminent())
586 					fFeaturedPackagesView->AddPackage(package);
587 			}
588 			break;
589 		}
590 
591 		case MSG_UPDATE_SELECTED_PACKAGE:
592 		{
593 			const PackageInfoRef& selectedPackage = fPackageInfoView->Package();
594 			fFeaturedPackagesView->SelectPackage(selectedPackage, true);
595 			fPackageListView->SelectPackage(selectedPackage);
596 
597 			AutoLocker<BLocker> modelLocker(fModel.Lock());
598 			if (!fVisiblePackages.Contains(fPackageInfoView->Package()))
599 				fPackageInfoView->Clear();
600 			break;
601 		}
602 
603 		default:
604 			BWindow::MessageReceived(message);
605 			break;
606 	}
607 }
608 
609 
610 void
611 MainWindow::StoreSettings(BMessage& settings) const
612 {
613 	settings.AddRect(_WindowFrameName(), Frame());
614 	if (!fSinglePackageMode) {
615 		settings.AddRect("window frame", Frame());
616 
617 		BMessage columnSettings;
618 		fPackageListView->SaveState(&columnSettings);
619 
620 		settings.AddMessage("column settings", &columnSettings);
621 
622 		settings.AddBool("show featured packages",
623 			fModel.ShowFeaturedPackages());
624 		settings.AddBool("show available packages",
625 			fModel.ShowAvailablePackages());
626 		settings.AddBool("show installed packages",
627 			fModel.ShowInstalledPackages());
628 		settings.AddBool("show develop packages", fModel.ShowDevelopPackages());
629 		settings.AddBool("show source packages", fModel.ShowSourcePackages());
630 	}
631 
632 	settings.AddString("username", fModel.Username());
633 }
634 
635 
636 void
637 MainWindow::PackageChanged(const PackageInfoEvent& event)
638 {
639 	uint32 whatchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE;
640 	if ((event.Changes() & whatchedChanges) != 0) {
641 		PackageInfoRef ref(event.Package());
642 		BMessage message(MSG_PACKAGE_CHANGED);
643 		message.AddPointer("package", ref.Get());
644 		message.AddUInt32("changes", event.Changes());
645 		ref.Detach();
646 			// reference needs to be released by MessageReceived();
647 		PostMessage(&message);
648 	}
649 }
650 
651 
652 status_t
653 MainWindow::SchedulePackageActions(PackageActionList& list)
654 {
655 	AutoLocker<BLocker> lock(&fPendingActionsLock);
656 	for (int32 i = 0; i < list.CountItems(); i++) {
657 		if (!fPendingActions.Add(list.ItemAtFast(i)))
658 			return B_NO_MEMORY;
659 	}
660 
661 	return release_sem_etc(fPendingActionsSem, list.CountItems(), 0);
662 }
663 
664 
665 Model*
666 MainWindow::GetModel()
667 {
668 	return &fModel;
669 }
670 
671 
672 void
673 MainWindow::_BuildMenu(BMenuBar* menuBar)
674 {
675 	BMenu* menu = new BMenu(B_TRANSLATE("Tools"));
676 	menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh repositories"),
677 		new BMessage(MSG_REFRESH_REPOS)));
678 	menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories"
679 		B_UTF8_ELLIPSIS), new BMessage(MSG_MANAGE_REPOS)));
680 	menu->AddItem(new BMenuItem(B_TRANSLATE("Check for updates"
681 		B_UTF8_ELLIPSIS), new BMessage(MSG_SOFTWARE_UPDATER)));
682 
683 	menuBar->AddItem(menu);
684 
685 	menu = new BMenu(B_TRANSLATE("Show"));
686 
687 	fShowFeaturedPackagesItem = new BMenuItem(
688 		B_TRANSLATE("Only featured packages"),
689 		new BMessage(MSG_SHOW_FEATURED_PACKAGES));
690 	menu->AddItem(fShowFeaturedPackagesItem);
691 
692 	menu->AddSeparatorItem();
693 
694 	fShowAvailablePackagesItem = new BMenuItem(
695 		B_TRANSLATE("Available packages"),
696 		new BMessage(MSG_SHOW_AVAILABLE_PACKAGES));
697 	menu->AddItem(fShowAvailablePackagesItem);
698 
699 	fShowInstalledPackagesItem = new BMenuItem(
700 		B_TRANSLATE("Installed packages"),
701 		new BMessage(MSG_SHOW_INSTALLED_PACKAGES));
702 	menu->AddItem(fShowInstalledPackagesItem);
703 
704 	menu->AddSeparatorItem();
705 
706 	fShowDevelopPackagesItem = new BMenuItem(
707 		B_TRANSLATE("Develop packages"),
708 		new BMessage(MSG_SHOW_DEVELOP_PACKAGES));
709 	menu->AddItem(fShowDevelopPackagesItem);
710 
711 	fShowSourcePackagesItem = new BMenuItem(
712 		B_TRANSLATE("Source packages"),
713 		new BMessage(MSG_SHOW_SOURCE_PACKAGES));
714 	menu->AddItem(fShowSourcePackagesItem);
715 
716 	menuBar->AddItem(menu);
717 }
718 
719 
720 void
721 MainWindow::_BuildUserMenu(BMenuBar* menuBar)
722 {
723 	fUserMenu = new BMenu(B_TRANSLATE("Not logged in"));
724 
725 	fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS),
726 		new BMessage(MSG_LOG_IN));
727 	fUserMenu->AddItem(fLogInItem);
728 
729 	fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"),
730 		new BMessage(MSG_LOG_OUT));
731 	fUserMenu->AddItem(fLogOutItem);
732 
733 	menuBar->AddItem(fUserMenu);
734 }
735 
736 
737 void
738 MainWindow::_RestoreUserName(const BMessage& settings)
739 {
740 	BString username;
741 	if (settings.FindString("username", &username) == B_OK
742 		&& username.Length() > 0) {
743 		fModel.SetUsername(username);
744 	}
745 }
746 
747 
748 const char*
749 MainWindow::_WindowFrameName() const
750 {
751 	if (fSinglePackageMode)
752 		return "small window frame";
753 
754 	return "window frame";
755 }
756 
757 
758 void
759 MainWindow::_RestoreWindowFrame(const BMessage& settings)
760 {
761 	BRect frame = Frame();
762 
763 	BRect windowFrame;
764 	bool fromSettings = false;
765 	if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) {
766 		frame = windowFrame;
767 		fromSettings = true;
768 	} else if (!fSinglePackageMode) {
769 		// Resize to occupy a certain screen size
770 		BRect screenFrame = BScreen(this).Frame();
771 		float width = frame.Width();
772 		float height = frame.Height();
773 		if (width < screenFrame.Width() * .666f
774 			&& height < screenFrame.Height() * .666f) {
775 			frame.bottom = frame.top + screenFrame.Height() * .666f;
776 			frame.right = frame.left
777 				+ std::min(screenFrame.Width() * .666f, height * 7 / 5);
778 		}
779 	}
780 
781 	MoveTo(frame.LeftTop());
782 	ResizeTo(frame.Width(), frame.Height());
783 
784 	if (fromSettings)
785 		MoveOnScreen();
786 	else
787 		CenterOnScreen();
788 }
789 
790 
791 void
792 MainWindow::_InitWorkerThreads()
793 {
794 	fPendingActionsSem = create_sem(0, "PendingPackageActions");
795 	if (fPendingActionsSem >= 0) {
796 		fPendingActionsWorker = spawn_thread(&_PackageActionWorker,
797 			"Planet Express", B_NORMAL_PRIORITY, this);
798 		if (fPendingActionsWorker >= 0)
799 			resume_thread(fPendingActionsWorker);
800 	} else
801 		fPendingActionsWorker = -1;
802 
803 	fPackageToPopulateSem = create_sem(0, "PopulatePackage");
804 	if (fPackageToPopulateSem >= 0) {
805 		fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker,
806 			"Package Populator", B_NORMAL_PRIORITY, this);
807 		if (fPopulatePackageWorker >= 0)
808 			resume_thread(fPopulatePackageWorker);
809 	} else
810 		fPopulatePackageWorker = -1;
811 
812 	fNewPackagesToShowSem = create_sem(0, "ShowPackages");
813 	fShowPackagesAcknowledgeSem = create_sem(0, "ShowPackagesAck");
814 	if (fNewPackagesToShowSem >= 0 && fShowPackagesAcknowledgeSem >= 0) {
815 		fShowPackagesWorker = spawn_thread(&_PackagesToShowWorker,
816 			"Good news everyone", B_NORMAL_PRIORITY, this);
817 		if (fShowPackagesWorker >= 0)
818 			resume_thread(fShowPackagesWorker);
819 	} else
820 		fShowPackagesWorker = -1;
821 }
822 
823 
824 void
825 MainWindow::_AdoptModel()
826 {
827 	fVisiblePackages = fModel.CreatePackageList();
828 
829 	{
830 		AutoLocker<BLocker> modelLocker(fModel.Lock());
831 		AutoLocker<BLocker> listLocker(fPackagesToShowListLock);
832 		fPackagesToShowList = fVisiblePackages;
833 		atomic_add(&fPackagesToShowListID, 1);
834 	}
835 
836 	fFeaturedPackagesView->Clear();
837 	fPackageListView->Clear();
838 
839 	release_sem(fNewPackagesToShowSem);
840 
841 	BAutolock locker(fModel.Lock());
842 	fShowFeaturedPackagesItem->SetMarked(fModel.ShowFeaturedPackages());
843 	fShowFeaturedPackagesItem->SetEnabled(fModel.SearchTerms() == "");
844 	fShowAvailablePackagesItem->SetMarked(fModel.ShowAvailablePackages());
845 	fShowInstalledPackagesItem->SetMarked(fModel.ShowInstalledPackages());
846 	fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages());
847 	fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages());
848 
849 	if (fModel.ShowFeaturedPackages() && fModel.SearchTerms() == "")
850 		fListLayout->SetVisibleItem((int32)0);
851 	else
852 		fListLayout->SetVisibleItem((int32)1);
853 }
854 
855 
856 void
857 MainWindow::_AdoptPackage(const PackageInfoRef& package)
858 {
859 	{
860 		BAutolock locker(fModel.Lock());
861 		fPackageInfoView->SetPackage(package);
862 
863 		if (fFeaturedPackagesView != NULL)
864 			fFeaturedPackagesView->SelectPackage(package);
865 		if (fPackageListView != NULL)
866 			fPackageListView->SelectPackage(package);
867 	}
868 
869 	// Trigger asynchronous package population from the web-app
870 	{
871 		AutoLocker<BLocker> lock(&fPackageToPopulateLock);
872 		fPackageToPopulate = package;
873 	}
874 	release_sem_etc(fPackageToPopulateSem, 1, 0);
875 }
876 
877 
878 void
879 MainWindow::_ClearPackage()
880 {
881 	fPackageInfoView->Clear();
882 }
883 
884 
885 void
886 MainWindow::_RefreshRepositories(bool force)
887 {
888 	if (fSinglePackageMode)
889 		return;
890 
891 	BPackageRoster roster;
892 	BStringList repositoryNames;
893 
894 	status_t result = roster.GetRepositoryNames(repositoryNames);
895 	if (result != B_OK)
896 		return;
897 
898 	DecisionProvider decisionProvider;
899 	JobStateListener listener;
900 	BContext context(decisionProvider, listener);
901 
902 	BRepositoryCache cache;
903 	for (int32 i = 0; i < repositoryNames.CountStrings(); ++i) {
904 		const BString& repoName = repositoryNames.StringAt(i);
905 		BRepositoryConfig repoConfig;
906 		result = roster.GetRepositoryConfig(repoName, &repoConfig);
907 		if (result != B_OK) {
908 			// TODO: notify user
909 			continue;
910 		}
911 
912 		if (roster.GetRepositoryCache(repoName, &cache) != B_OK || force) {
913 			try {
914 				BRefreshRepositoryRequest refreshRequest(context, repoConfig);
915 
916 				result = refreshRequest.Process();
917 			} catch (BFatalErrorException ex) {
918 				BString message(B_TRANSLATE("An error occurred while "
919 					"refreshing the repository: %error% (%details%)"));
920  				message.ReplaceFirst("%error%", ex.Message());
921 				message.ReplaceFirst("%details%", ex.Details());
922 				_NotifyUser("Error", message.String());
923 			} catch (BException ex) {
924 				BString message(B_TRANSLATE("An error occurred while "
925 					"refreshing the repository: %error%"));
926 				message.ReplaceFirst("%error%", ex.Message());
927 				_NotifyUser("Error", message.String());
928 			}
929 		}
930 	}
931 }
932 
933 
934 void
935 MainWindow::_RefreshPackageList(bool force)
936 {
937 	if (fSinglePackageMode)
938 		return;
939 
940 	BPackageRoster roster;
941 	BStringList repositoryNames;
942 
943 	status_t result = roster.GetRepositoryNames(repositoryNames);
944 	if (result != B_OK)
945 		return;
946 
947 	DepotInfoMap depots;
948 	for (int32 i = 0; i < repositoryNames.CountStrings(); i++) {
949 		const BString& repoName = repositoryNames.StringAt(i);
950 		DepotInfo depotInfo = DepotInfo(repoName);
951 
952 		BRepositoryConfig repoConfig;
953 		status_t getRepositoryConfigStatus = roster.GetRepositoryConfig(
954 			repoName, &repoConfig);
955 
956 		if (getRepositoryConfigStatus == B_OK) {
957 			depotInfo.SetBaseURL(repoConfig.BaseURL());
958 		} else {
959 			printf("unable to obtain the repository config for local "
960 				"repository '%s'; %s\n",
961 				repoName.String(), strerror(getRepositoryConfigStatus));
962 		}
963 
964 		depots[repoName] = depotInfo;
965 	}
966 
967 	PackageManager manager(B_PACKAGE_INSTALLATION_LOCATION_HOME);
968 	try {
969 		manager.Init(PackageManager::B_ADD_INSTALLED_REPOSITORIES
970 			| PackageManager::B_ADD_REMOTE_REPOSITORIES);
971 	} catch (BException ex) {
972 		BString message(B_TRANSLATE("An error occurred while "
973 			"initializing the package manager: %message%"));
974 		message.ReplaceFirst("%message%", ex.Message());
975 		_NotifyUser("Error", message.String());
976 		return;
977 	}
978 
979 	BObjectList<BSolverPackage> packages;
980 	result = manager.Solver()->FindPackages("",
981 		BSolver::B_FIND_CASE_INSENSITIVE | BSolver::B_FIND_IN_NAME
982 			| BSolver::B_FIND_IN_SUMMARY | BSolver::B_FIND_IN_DESCRIPTION
983 			| BSolver::B_FIND_IN_PROVIDES,
984 		packages);
985 	if (result != B_OK) {
986 		BString message(B_TRANSLATE("An error occurred while "
987 			"obtaining the package list: %message%"));
988 		message.ReplaceFirst("%message%", strerror(result));
989 		_NotifyUser("Error", message.String());
990 		return;
991 	}
992 
993 	if (packages.IsEmpty())
994 		return;
995 
996 	PackageInfoMap foundPackages;
997 		// if a given package is installed locally, we will potentially
998 		// get back multiple entries, one for each local installation
999 		// location, and one for each remote repository the package
1000 		// is available in. The above map is used to ensure that in such
1001 		// cases we consolidate the information, rather than displaying
1002 		// duplicates
1003 	PackageInfoMap remotePackages;
1004 		// any package that we find in a remote repository goes in this map.
1005 		// this is later used to discern which packages came from a local
1006 		// installation only, as those must be handled a bit differently
1007 		// upon uninstallation, since we'd no longer be able to pull them
1008 		// down remotely.
1009 	BStringList systemFlaggedPackages;
1010 		// any packages flagged as a system package are added to this list.
1011 		// such packages cannot be uninstalled, nor can any of their deps.
1012 	PackageInfoMap systemInstalledPackages;
1013 		// any packages installed in system are added to this list.
1014 		// This is later used for dependency resolution of the actual
1015 		// system packages in order to compute the list of protected
1016 		// dependencies indicated above.
1017 
1018 	for (int32 i = 0; i < packages.CountItems(); i++) {
1019 		BSolverPackage* package = packages.ItemAt(i);
1020 		const BPackageInfo& repoPackageInfo = package->Info();
1021 		const BString repositoryName = package->Repository()->Name();
1022 		PackageInfoRef modelInfo;
1023 		PackageInfoMap::iterator it = foundPackages.find(
1024 			repoPackageInfo.Name());
1025 		if (it != foundPackages.end())
1026 			modelInfo.SetTo(it->second);
1027 		else {
1028 			// Add new package info
1029 			modelInfo.SetTo(new(std::nothrow) PackageInfo(repoPackageInfo),
1030 				true);
1031 
1032 			if (modelInfo.Get() == NULL)
1033 				return;
1034 
1035 			modelInfo->SetDepotName(repositoryName);
1036 
1037 			foundPackages[repoPackageInfo.Name()] = modelInfo;
1038 		}
1039 
1040 		modelInfo->AddListener(this);
1041 
1042 		BSolverRepository* repository = package->Repository();
1043 		if (dynamic_cast<BPackageManager::RemoteRepository*>(repository)
1044 				!= NULL) {
1045 			depots[repository->Name()].AddPackage(modelInfo);
1046 			remotePackages[modelInfo->Name()] = modelInfo;
1047 		} else {
1048 			if (repository == static_cast<const BSolverRepository*>(
1049 					manager.SystemRepository())) {
1050 				modelInfo->AddInstallationLocation(
1051 					B_PACKAGE_INSTALLATION_LOCATION_SYSTEM);
1052 				if (!modelInfo->IsSystemPackage()) {
1053 					systemInstalledPackages[repoPackageInfo.FileName()]
1054 						= modelInfo;
1055 				}
1056 			} else if (repository == static_cast<const BSolverRepository*>(
1057 					manager.HomeRepository())) {
1058 				modelInfo->AddInstallationLocation(
1059 					B_PACKAGE_INSTALLATION_LOCATION_HOME);
1060 			}
1061 		}
1062 
1063 		if (modelInfo->IsSystemPackage())
1064 			systemFlaggedPackages.Add(repoPackageInfo.FileName());
1065 	}
1066 
1067 	bool wasEmpty = fModel.Depots().IsEmpty();
1068 	if (force || wasEmpty)
1069 		fBulkLoadStateMachine.Stop();
1070 
1071 	BAutolock lock(fModel.Lock());
1072 
1073 	if (force)
1074 		fModel.Clear();
1075 
1076 	// filter remote packages from the found list
1077 	// any packages remaining will be locally installed packages
1078 	// that weren't acquired from a repository
1079 	for (PackageInfoMap::iterator it = remotePackages.begin();
1080 			it != remotePackages.end(); it++) {
1081 		foundPackages.erase(it->first);
1082 	}
1083 
1084 	if (!foundPackages.empty()) {
1085 		BString repoName = B_TRANSLATE("Local");
1086 		depots[repoName] = DepotInfo(repoName);
1087 		DepotInfoMap::iterator depot = depots.find(repoName);
1088 		for (PackageInfoMap::iterator it = foundPackages.begin();
1089 				it != foundPackages.end(); ++it) {
1090 			depot->second.AddPackage(it->second);
1091 		}
1092 	}
1093 
1094 	for (DepotInfoMap::iterator it = depots.begin(); it != depots.end(); it++) {
1095 		if (fModel.HasDepot(it->second.Name()))
1096 			fModel.SyncDepot(it->second);
1097 		else
1098 			fModel.AddDepot(it->second);
1099 	}
1100 
1101 	// start retrieving package icons and average ratings
1102 	if (force || wasEmpty) {
1103 			fBulkLoadStateMachine.Start();
1104 	}
1105 
1106 	// compute the OS package dependencies
1107 	try {
1108 		// create the solver
1109 		BSolver* solver;
1110 		status_t error = BSolver::Create(solver);
1111 		if (error != B_OK)
1112 			throw BFatalErrorException(error, "Failed to create solver.");
1113 
1114 		ObjectDeleter<BSolver> solverDeleter(solver);
1115 		BPath systemPath;
1116 		error = find_directory(B_SYSTEM_PACKAGES_DIRECTORY, &systemPath);
1117 		if (error != B_OK) {
1118 			throw BFatalErrorException(error,
1119 				"Unable to retrieve system packages directory.");
1120 		}
1121 
1122 		// add the "installed" repository with the given packages
1123 		BSolverRepository installedRepository;
1124 		{
1125 			BRepositoryBuilder installedRepositoryBuilder(installedRepository,
1126 				"installed");
1127 			for (int32 i = 0; i < systemFlaggedPackages.CountStrings(); i++) {
1128 				BPath packagePath(systemPath);
1129 				packagePath.Append(systemFlaggedPackages.StringAt(i));
1130 				installedRepositoryBuilder.AddPackage(packagePath.Path());
1131 			}
1132 			installedRepositoryBuilder.AddToSolver(solver, true);
1133 		}
1134 
1135 		// add system repository
1136 		BSolverRepository systemRepository;
1137 		{
1138 			BRepositoryBuilder systemRepositoryBuilder(systemRepository,
1139 				"system");
1140 			for (PackageInfoMap::iterator it = systemInstalledPackages.begin();
1141 					it != systemInstalledPackages.end(); it++) {
1142 				BPath packagePath(systemPath);
1143 				packagePath.Append(it->first);
1144 				systemRepositoryBuilder.AddPackage(packagePath.Path());
1145 			}
1146 			systemRepositoryBuilder.AddToSolver(solver, false);
1147 		}
1148 
1149 		// solve
1150 		error = solver->VerifyInstallation();
1151 		if (error != B_OK) {
1152 			throw BFatalErrorException(error, "Failed to compute packages to "
1153 				"install.");
1154 		}
1155 
1156 		BSolverResult solverResult;
1157 		error = solver->GetResult(solverResult);
1158 		if (error != B_OK) {
1159 			throw BFatalErrorException(error, "Failed to retrieve system "
1160 				"package dependency list.");
1161 		}
1162 
1163 		for (int32 i = 0; const BSolverResultElement* element
1164 				= solverResult.ElementAt(i); i++) {
1165 			BSolverPackage* package = element->Package();
1166 			if (element->Type() == BSolverResultElement::B_TYPE_INSTALL) {
1167 				PackageInfoMap::iterator it = systemInstalledPackages.find(
1168 					package->Info().FileName());
1169 				if (it != systemInstalledPackages.end())
1170 					it->second->SetSystemDependency(true);
1171 			}
1172 		}
1173 	} catch (BFatalErrorException ex) {
1174 		printf("Fatal exception occurred while resolving system dependencies: "
1175 			"%s, details: %s\n", strerror(ex.Error()), ex.Details().String());
1176 	} catch (BNothingToDoException) {
1177 		// do nothing
1178 	} catch (BException ex) {
1179 		printf("Exception occurred while resolving system dependencies: %s\n",
1180 			ex.Message().String());
1181 	} catch (...) {
1182 		printf("Unknown exception occurred while resolving system "
1183 			"dependencies.\n");
1184 	}
1185 }
1186 
1187 
1188 void
1189 MainWindow::_StartRefreshWorker(bool force)
1190 {
1191 	if (fModelWorker != B_BAD_THREAD_ID)
1192 		return;
1193 
1194 	RefreshWorkerParameters* parameters = new(std::nothrow)
1195 		RefreshWorkerParameters(this, force);
1196 	if (parameters == NULL)
1197 		return;
1198 
1199 	fWorkStatusView->SetBusy(B_TRANSLATE("Refreshing..."));
1200 
1201 	ObjectDeleter<RefreshWorkerParameters> deleter(parameters);
1202 	fModelWorker = spawn_thread(&_RefreshModelThreadWorker, "model loader",
1203 		B_LOW_PRIORITY, parameters);
1204 
1205 	if (fModelWorker > 0) {
1206 		deleter.Detach();
1207 		resume_thread(fModelWorker);
1208 	}
1209 }
1210 
1211 
1212 status_t
1213 MainWindow::_RefreshModelThreadWorker(void* arg)
1214 {
1215 	RefreshWorkerParameters* parameters
1216 		= reinterpret_cast<RefreshWorkerParameters*>(arg);
1217 	MainWindow* mainWindow = parameters->window;
1218 	ObjectDeleter<RefreshWorkerParameters> deleter(parameters);
1219 
1220 	BMessenger messenger(mainWindow);
1221 
1222 	mainWindow->_RefreshRepositories(parameters->forceRefresh);
1223 
1224 	if (mainWindow->fTerminating)
1225 		return B_OK;
1226 
1227 	mainWindow->_RefreshPackageList(parameters->forceRefresh);
1228 
1229 	messenger.SendMessage(MSG_MODEL_WORKER_DONE);
1230 
1231 	return B_OK;
1232 }
1233 
1234 
1235 status_t
1236 MainWindow::_PackageActionWorker(void* arg)
1237 {
1238 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1239 
1240 	while (acquire_sem(window->fPendingActionsSem) == B_OK) {
1241 		PackageActionRef ref;
1242 		{
1243 			AutoLocker<BLocker> lock(&window->fPendingActionsLock);
1244 			ref = window->fPendingActions.ItemAt(0);
1245 			if (ref.Get() == NULL)
1246 				break;
1247 			window->fPendingActions.Remove(0);
1248 		}
1249 
1250 		BMessenger messenger(window);
1251 		BMessage busyMessage(MSG_PACKAGE_WORKER_BUSY);
1252 		BString text(ref->Label());
1253 		text << "...";
1254 		busyMessage.AddString("reason", text);
1255 
1256 		messenger.SendMessage(&busyMessage);
1257 		ref->Perform();
1258 		messenger.SendMessage(MSG_PACKAGE_WORKER_IDLE);
1259 	}
1260 
1261 	return 0;
1262 }
1263 
1264 
1265 status_t
1266 MainWindow::_PopulatePackageWorker(void* arg)
1267 {
1268 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1269 
1270 	while (acquire_sem(window->fPackageToPopulateSem) == B_OK) {
1271 		PackageInfoRef package;
1272 		{
1273 			AutoLocker<BLocker> lock(&window->fPackageToPopulateLock);
1274 			package = window->fPackageToPopulate;
1275 		}
1276 
1277 		if (package.Get() != NULL) {
1278 			window->fModel.PopulatePackage(package,
1279 				Model::POPULATE_USER_RATINGS | Model::POPULATE_SCREEN_SHOTS
1280 					| Model::POPULATE_CHANGELOG);
1281 		}
1282 	}
1283 
1284 	return 0;
1285 }
1286 
1287 
1288 /* static */ status_t
1289 MainWindow::_PackagesToShowWorker(void* arg)
1290 {
1291 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1292 
1293 	while (acquire_sem(window->fNewPackagesToShowSem) == B_OK) {
1294 		PackageList packageList;
1295 		int32 listID = 0;
1296 		{
1297 			AutoLocker<BLocker> lock(&window->fPackagesToShowListLock);
1298 			packageList = window->fPackagesToShowList;
1299 			listID = atomic_get(&window->fPackagesToShowListID);
1300 			window->fPackagesToShowList.Clear();
1301 		}
1302 
1303 		// Add packages to list views in batches of kPackagesPerUpdate so we
1304 		// don't block the window thread for long with each iteration
1305 		enum {
1306 			kPackagesPerUpdate = 20
1307 		};
1308 		uint32 packagesInMessage = 0;
1309 		BMessage message(MSG_ADD_VISIBLE_PACKAGES);
1310 		BMessenger messenger(window);
1311 		bool listIsOutdated = false;
1312 
1313 		for (int i = 0; i < packageList.CountItems(); i++) {
1314 			const PackageInfoRef& package = packageList.ItemAtFast(i);
1315 
1316 			if (packagesInMessage >= kPackagesPerUpdate) {
1317 				if (listID != atomic_get(&window->fPackagesToShowListID)) {
1318 					// The model was changed again in the meantime, and thus
1319 					// our package list isn't current anymore. Send no further
1320 					// messags based on this list and go back to start.
1321 					listIsOutdated = true;
1322 					break;
1323 				}
1324 
1325 				message.AddInt32("list_id", listID);
1326 				messenger.SendMessage(&message);
1327 				message.MakeEmpty();
1328 				packagesInMessage = 0;
1329 
1330 				// Don't spam the window's message queue, which would make it
1331 				// unresponsive (i.e. allows UI messages to get in between our
1332 				// messages). When it has processed the message we just sent,
1333 				// it will let us know by releasing the semaphore.
1334 				acquire_sem(window->fShowPackagesAcknowledgeSem);
1335 			}
1336 			package->AcquireReference();
1337 			message.AddPointer("package_ref", package.Get());
1338 			packagesInMessage++;
1339 		}
1340 
1341 		if (listIsOutdated)
1342 			continue;
1343 
1344 		// Send remaining package infos, if any, which didn't make it into
1345 		// the last message (count < kPackagesPerUpdate)
1346 		if (packagesInMessage > 0) {
1347 			message.AddInt32("list_id", listID);
1348 			messenger.SendMessage(&message);
1349 			acquire_sem(window->fShowPackagesAcknowledgeSem);
1350 		}
1351 
1352 		// Update selected package in list views
1353 		messenger.SendMessage(MSG_UPDATE_SELECTED_PACKAGE);
1354 	}
1355 
1356 	return 0;
1357 }
1358 
1359 
1360 void
1361 MainWindow::_NotifyUser(const char* title, const char* message)
1362 {
1363 	BAlert* alert = new(std::nothrow) BAlert(title, message,
1364 		B_TRANSLATE("Close"));
1365 
1366 	if (alert != NULL)
1367 		alert->Go();
1368 }
1369 
1370 
1371 void
1372 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage)
1373 {
1374 	UserLoginWindow* window = new UserLoginWindow(this,
1375 		BRect(0, 0, 500, 400), fModel);
1376 
1377 	if (onSuccessMessage.what != 0)
1378 		window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage);
1379 
1380 	window->Show();
1381 }
1382 
1383 
1384 void
1385 MainWindow::_UpdateAuthorization()
1386 {
1387 	BString username(fModel.Username());
1388 	bool hasUser = !username.IsEmpty();
1389 
1390 	if (fLogOutItem != NULL)
1391 		fLogOutItem->SetEnabled(hasUser);
1392 	if (fLogInItem != NULL) {
1393 		if (hasUser)
1394 			fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS));
1395 		else
1396 			fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS));
1397 	}
1398 
1399 	if (fUserMenu != NULL) {
1400 		BString label;
1401 		if (username.Length() == 0) {
1402 			label = B_TRANSLATE("Not logged in");
1403 		} else {
1404 			label = B_TRANSLATE("Logged in as %User%");
1405 			label.ReplaceAll("%User%", username);
1406 		}
1407 		fUserMenu->Superitem()->SetLabel(label);
1408 	}
1409 }
1410 
1411 
1412 void
1413 MainWindow::_RatePackage()
1414 {
1415 	if (fModel.Username().IsEmpty()) {
1416 		BAlert* alert = new(std::nothrow) BAlert(
1417 			B_TRANSLATE("Not logged in"),
1418 			B_TRANSLATE("You need to be logged into an account before you "
1419 				"can rate packages."),
1420 			B_TRANSLATE("Cancel"),
1421 			B_TRANSLATE("Login or Create account"));
1422 
1423 		if (alert == NULL)
1424 			return;
1425 
1426 		int32 choice = alert->Go();
1427 		if (choice == 1)
1428 			_OpenLoginWindow(BMessage(MSG_RATE_PACKAGE));
1429 		return;
1430 	}
1431 
1432 	// TODO: Allow only one RatePackageWindow
1433 	// TODO: Mechanism for remembering the window frame
1434 	RatePackageWindow* window = new RatePackageWindow(this,
1435 		BRect(0, 0, 500, 400), fModel);
1436 	window->SetPackage(fPackageInfoView->Package());
1437 	window->Show();
1438 }
1439 
1440 
1441 void
1442 MainWindow::_ShowScreenshot()
1443 {
1444 	// TODO: Mechanism for remembering the window frame
1445 	if (fScreenshotWindow == NULL)
1446 		fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400));
1447 
1448 	if (fScreenshotWindow->LockWithTimeout(1000) != B_OK)
1449 		return;
1450 
1451 	fScreenshotWindow->SetPackage(fPackageInfoView->Package());
1452 
1453 	if (fScreenshotWindow->IsHidden())
1454 		fScreenshotWindow->Show();
1455 	else
1456 		fScreenshotWindow->Activate();
1457 
1458 	fScreenshotWindow->Unlock();
1459 }
1460