xref: /haiku/src/apps/haikudepot/ui/MainWindow.cpp (revision 4b918abdb02a26a770d898594eaaccc6f1726e9b)
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-2019, 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 "UserLoginWindow.h"
51 #include "UserUsageConditionsWindow.h"
52 #include "WorkStatusView.h"
53 
54 
55 #undef B_TRANSLATION_CONTEXT
56 #define B_TRANSLATION_CONTEXT "MainWindow"
57 
58 
59 enum {
60 	MSG_BULK_LOAD_DONE						= 'mmwd',
61 	MSG_REFRESH_REPOS						= 'mrrp',
62 	MSG_MANAGE_REPOS						= 'mmrp',
63 	MSG_SOFTWARE_UPDATER					= 'mswu',
64 	MSG_LOG_IN								= 'lgin',
65 	MSG_LOG_OUT								= 'lgot',
66 	MSG_AUTHORIZATION_CHANGED				= 'athc',
67 	MSG_CATEGORIES_LIST_CHANGED				= 'clic',
68 	MSG_PACKAGE_CHANGED						= 'pchd',
69 	MSG_WORK_STATUS_CHANGE					= 'wsch',
70 	MSG_WORK_STATUS_CLEAR					= 'wscl',
71 
72 	MSG_SHOW_FEATURED_PACKAGES				= 'sofp',
73 	MSG_SHOW_AVAILABLE_PACKAGES				= 'savl',
74 	MSG_SHOW_INSTALLED_PACKAGES				= 'sins',
75 	MSG_SHOW_SOURCE_PACKAGES				= 'ssrc',
76 	MSG_SHOW_DEVELOP_PACKAGES				= 'sdvl'
77 };
78 
79 
80 using namespace BPackageKit;
81 using namespace BPackageKit::BManager::BPrivate;
82 
83 
84 typedef std::map<BString, PackageInfoRef> PackageInfoMap;
85 
86 
87 struct RefreshWorkerParameters {
88 	MainWindow* window;
89 	bool forceRefresh;
90 
91 	RefreshWorkerParameters(MainWindow* window, bool forceRefresh)
92 		:
93 		window(window),
94 		forceRefresh(forceRefresh)
95 	{
96 	}
97 };
98 
99 
100 class MainWindowModelListener : public ModelListener {
101 public:
102 	MainWindowModelListener(const BMessenger& messenger)
103 		:
104 		fMessenger(messenger)
105 	{
106 	}
107 
108 	virtual void AuthorizationChanged()
109 	{
110 		if (fMessenger.IsValid())
111 			fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED);
112 	}
113 
114 	virtual void CategoryListChanged()
115 	{
116 		if (fMessenger.IsValid())
117 			fMessenger.SendMessage(MSG_CATEGORIES_LIST_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 	fUsersUserUsageConditionsMenuItem(NULL),
135 	fModelListener(new MainWindowModelListener(BMessenger(this)), true),
136 	fBulkLoadProcessCoordinator(NULL),
137 	fSinglePackageMode(false)
138 {
139 	BMenuBar* menuBar = new BMenuBar("Main Menu");
140 	_BuildMenu(menuBar);
141 
142 	BMenuBar* userMenuBar = new BMenuBar("User Menu");
143 	_BuildUserMenu(userMenuBar);
144 	set_small_font(userMenuBar);
145 	userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET,
146 		menuBar->MaxSize().height));
147 	_UpdateAuthorization();
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 	fWorkStatusView = new WorkStatusView("work status");
157 	fPackageListView->AttachWorkStatusView(fWorkStatusView);
158 
159 	fListTabs = new TabView(BMessenger(this),
160 		BMessage(MSG_SHOW_FEATURED_PACKAGES), "list tabs");
161 	fListTabs->AddTab(fFeaturedPackagesView);
162 	fListTabs->AddTab(fPackageListView);
163 
164 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f)
165 		.AddGroup(B_HORIZONTAL, 0.0f)
166 			.Add(menuBar, 1.0f)
167 			.Add(userMenuBar, 0.0f)
168 		.End()
169 		.Add(fFilterView)
170 		.AddSplit(fSplitView)
171 			.AddGroup(B_VERTICAL)
172 				.Add(fListTabs)
173 				.SetInsets(
174 					B_USE_DEFAULT_SPACING, 0.0f,
175 					B_USE_DEFAULT_SPACING, 0.0f)
176 			.End()
177 			.Add(fPackageInfoView)
178 		.End()
179 		.Add(fWorkStatusView)
180 	;
181 
182 	fSplitView->SetCollapsible(0, false);
183 	fSplitView->SetCollapsible(1, false);
184 
185 	fModel.AddListener(fModelListener);
186 
187 	// Restore settings
188 	BMessage columnSettings;
189 	if (settings.FindMessage("column settings", &columnSettings) == B_OK)
190 		fPackageListView->LoadState(&columnSettings);
191 
192 	bool showOption;
193 	if (settings.FindBool("show featured packages", &showOption) == B_OK)
194 		fModel.SetShowFeaturedPackages(showOption);
195 	if (settings.FindBool("show available packages", &showOption) == B_OK)
196 		fModel.SetShowAvailablePackages(showOption);
197 	if (settings.FindBool("show installed packages", &showOption) == B_OK)
198 		fModel.SetShowInstalledPackages(showOption);
199 	if (settings.FindBool("show develop packages", &showOption) == B_OK)
200 		fModel.SetShowDevelopPackages(showOption);
201 	if (settings.FindBool("show source packages", &showOption) == B_OK)
202 		fModel.SetShowSourcePackages(showOption);
203 
204 	if (fModel.ShowFeaturedPackages())
205 		fListTabs->Select(0);
206 	else
207 		fListTabs->Select(1);
208 
209 	_RestoreNickname(settings);
210 	_RestoreWindowFrame(settings);
211 
212 	atomic_set(&fPackagesToShowListID, 0);
213 
214 	// start worker threads
215 	BPackageRoster().StartWatching(this,
216 		B_WATCH_PACKAGE_INSTALLATION_LOCATIONS);
217 
218 	_StartBulkLoad();
219 
220 	_InitWorkerThreads();
221 }
222 
223 
224 MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package)
225 	:
226 	BWindow(BRect(50, 50, 650, 350), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
227 		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
228 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
229 	fWorkStatusView(NULL),
230 	fScreenshotWindow(NULL),
231 	fUserMenu(NULL),
232 	fLogInItem(NULL),
233 	fLogOutItem(NULL),
234 	fUsersUserUsageConditionsMenuItem(NULL),
235 	fModelListener(new MainWindowModelListener(BMessenger(this)), true),
236 	fBulkLoadProcessCoordinator(NULL),
237 	fSinglePackageMode(true)
238 {
239 	fFilterView = new FilterView();
240 	fPackageListView = new PackageListView(fModel.Lock());
241 	fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
242 
243 	BLayoutBuilder::Group<>(this, B_VERTICAL)
244 		.Add(fPackageInfoView)
245 		.SetInsets(0, B_USE_WINDOW_INSETS, 0, 0)
246 	;
247 
248 	fModel.AddListener(fModelListener);
249 
250 	// Restore settings
251 	_RestoreNickname(settings);
252 	_RestoreWindowFrame(settings);
253 
254 	fPackageInfoView->SetPackage(package);
255 
256 	_InitWorkerThreads();
257 }
258 
259 
260 MainWindow::~MainWindow()
261 {
262 	BPackageRoster().StopWatching(this);
263 
264 	delete_sem(fPendingActionsSem);
265 	if (fPendingActionsWorker >= 0)
266 		wait_for_thread(fPendingActionsWorker, NULL);
267 
268 	delete_sem(fPackageToPopulateSem);
269 	if (fPopulatePackageWorker >= 0)
270 		wait_for_thread(fPopulatePackageWorker, NULL);
271 
272 	delete_sem(fNewPackagesToShowSem);
273 	delete_sem(fShowPackagesAcknowledgeSem);
274 	if (fShowPackagesWorker >= 0)
275 		wait_for_thread(fShowPackagesWorker, NULL);
276 
277 	if (fScreenshotWindow != NULL && fScreenshotWindow->Lock())
278 		fScreenshotWindow->Quit();
279 }
280 
281 
282 bool
283 MainWindow::QuitRequested()
284 {
285 	BMessage settings;
286 	StoreSettings(settings);
287 
288 	BMessage message(MSG_MAIN_WINDOW_CLOSED);
289 	message.AddMessage(KEY_WINDOW_SETTINGS, &settings);
290 
291 	be_app->PostMessage(&message);
292 
293 	_StopBulkLoad();
294 
295 	return true;
296 }
297 
298 
299 void
300 MainWindow::MessageReceived(BMessage* message)
301 {
302 	switch (message->what) {
303 		case MSG_BULK_LOAD_DONE:
304 			_BulkLoadCompleteReceived();
305 			break;
306 		case B_SIMPLE_DATA:
307 		case B_REFS_RECEIVED:
308 			// TODO: ?
309 			break;
310 
311 		case B_PACKAGE_UPDATE:
312 			// TODO: We should do a more selective update depending on the
313 			// "event", "location", and "change count" fields!
314 			_StartBulkLoad(false);
315 			break;
316 
317 		case MSG_REFRESH_REPOS:
318 			_StartBulkLoad(true);
319 			break;
320 
321 		case MSG_WORK_STATUS_CHANGE:
322 			_HandleWorkStatusChangeMessageReceived(message);
323 			break;
324 
325 		case MSG_MANAGE_REPOS:
326 			be_roster->Launch("application/x-vnd.Haiku-Repositories");
327 			break;
328 
329 		case MSG_SOFTWARE_UPDATER:
330 			be_roster->Launch("application/x-vnd.haiku-softwareupdater");
331 			break;
332 
333 		case MSG_LOG_IN:
334 			_OpenLoginWindow(BMessage());
335 			break;
336 
337 		case MSG_LOG_OUT:
338 			fModel.SetNickname("");
339 			break;
340 
341 		case MSG_VIEW_LATEST_USER_USAGE_CONDITIONS:
342 			_ViewUserUsageConditions(LATEST);
343 			break;
344 
345 		case MSG_VIEW_USERS_USER_USAGE_CONDITIONS:
346 			_ViewUserUsageConditions(USER);
347 			break;
348 
349 		case MSG_AUTHORIZATION_CHANGED:
350 			_UpdateAuthorization();
351 			break;
352 
353 		case MSG_CATEGORIES_LIST_CHANGED:
354 			fFilterView->AdoptModel(fModel);
355 			break;
356 
357 		case MSG_SHOW_FEATURED_PACKAGES:
358 			// check to see if we aren't already on the current tab
359 			if (fListTabs->Selection() ==
360 					(fModel.ShowFeaturedPackages() ? 0 : 1))
361 				break;
362 			{
363 				BAutolock locker(fModel.Lock());
364 				fModel.SetShowFeaturedPackages(
365 					fListTabs->Selection() == 0);
366 			}
367 			_AdoptModel();
368 			break;
369 
370 		case MSG_SHOW_AVAILABLE_PACKAGES:
371 			{
372 				BAutolock locker(fModel.Lock());
373 				fModel.SetShowAvailablePackages(
374 					!fModel.ShowAvailablePackages());
375 			}
376 			_AdoptModel();
377 			break;
378 
379 		case MSG_SHOW_INSTALLED_PACKAGES:
380 			{
381 				BAutolock locker(fModel.Lock());
382 				fModel.SetShowInstalledPackages(
383 					!fModel.ShowInstalledPackages());
384 			}
385 			_AdoptModel();
386 			break;
387 
388 		case MSG_SHOW_SOURCE_PACKAGES:
389 			{
390 				BAutolock locker(fModel.Lock());
391 				fModel.SetShowSourcePackages(!fModel.ShowSourcePackages());
392 			}
393 			_AdoptModel();
394 			break;
395 
396 		case MSG_SHOW_DEVELOP_PACKAGES:
397 			{
398 				BAutolock locker(fModel.Lock());
399 				fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages());
400 			}
401 			_AdoptModel();
402 			break;
403 
404 			// this may be triggered by, for example, a user rating being added
405 			// or having been altered.
406 		case MSG_SERVER_DATA_CHANGED:
407 		{
408 			BString name;
409 			if (message->FindString("name", &name) == B_OK) {
410 				BAutolock locker(fModel.Lock());
411 				if (fPackageInfoView->Package()->Name() == name) {
412 					_PopulatePackageAsync(true);
413 				} else {
414 					if (Logger::IsDebugEnabled()) {
415 						printf("pkg [%s] is updated on the server, but is "
416 							"not selected so will not be updated.\n",
417 							name.String());
418 					}
419 				}
420 			}
421         	break;
422         }
423 
424 		case MSG_PACKAGE_SELECTED:
425 		{
426 			BString name;
427 			if (message->FindString("name", &name) == B_OK) {
428 				BAutolock locker(fModel.Lock());
429 				int count = fVisiblePackages.CountItems();
430 				for (int i = 0; i < count; i++) {
431 					const PackageInfoRef& package
432 						= fVisiblePackages.ItemAtFast(i);
433 					if (package.Get() != NULL && package->Name() == name) {
434 						locker.Unlock();
435 						_AdoptPackage(package);
436 						break;
437 					}
438 				}
439 			} else {
440 				_ClearPackage();
441 			}
442 			break;
443 		}
444 
445 		case MSG_CATEGORY_SELECTED:
446 		{
447 			BString code;
448 			if (message->FindString("code", &code) != B_OK)
449 				code = "";
450 			{
451 				BAutolock locker(fModel.Lock());
452 				fModel.SetCategory(code);
453 			}
454 			_AdoptModel();
455 			break;
456 		}
457 
458 		case MSG_DEPOT_SELECTED:
459 		{
460 			BString name;
461 			if (message->FindString("name", &name) != B_OK)
462 				name = "";
463 			{
464 				BAutolock locker(fModel.Lock());
465 				fModel.SetDepot(name);
466 			}
467 			_AdoptModel();
468 			_UpdateAvailableRepositories();
469 			break;
470 		}
471 
472 		case MSG_SEARCH_TERMS_MODIFIED:
473 		{
474 			// TODO: Do this with a delay!
475 			BString searchTerms;
476 			if (message->FindString("search terms", &searchTerms) != B_OK)
477 				searchTerms = "";
478 			{
479 				BAutolock locker(fModel.Lock());
480 				fModel.SetSearchTerms(searchTerms);
481 			}
482 			_AdoptModel();
483 			break;
484 		}
485 
486 		case MSG_PACKAGE_CHANGED:
487 		{
488 			PackageInfo* info;
489 			if (message->FindPointer("package", (void**)&info) == B_OK) {
490 				PackageInfoRef ref(info, true);
491 				uint32 changes;
492 				if (message->FindUInt32("changes", &changes) != B_OK)
493 					changes = 0;
494 				if ((changes & PKG_CHANGED_STATE) != 0) {
495 					BAutolock locker(fModel.Lock());
496 					fModel.SetPackageState(ref, ref->State());
497 				}
498 
499 				// Asynchronous updates to the package information
500 				// can mean that the package needs to be added or
501 				// removed to/from the visible packages when the current
502 				// filter parameters are re-evaluated on this package.
503 				bool wasVisible = fVisiblePackages.Contains(ref);
504 				bool isVisible;
505 				{
506 					BAutolock locker(fModel.Lock());
507 					// The package didn't get a chance yet to be in the
508 					// visible package list
509 					isVisible = fModel.MatchesFilter(ref);
510 
511 					// Transfer this single package, otherwise we miss
512 					// other packages if they appear or disappear along
513 					// with this one before receive a notification for
514 					// them.
515 					if (isVisible) {
516 						fVisiblePackages.Add(ref);
517 					} else if (wasVisible)
518 						fVisiblePackages.Remove(ref);
519 				}
520 
521 				if (wasVisible != isVisible) {
522 					if (!isVisible) {
523 						fPackageListView->RemovePackage(ref);
524 						fFeaturedPackagesView->RemovePackage(ref);
525 					} else {
526 						fPackageListView->AddPackage(ref);
527 						if (ref->IsProminent())
528 							fFeaturedPackagesView->AddPackage(ref);
529 					}
530 				}
531 
532 				if (!fSinglePackageMode && (changes & PKG_CHANGED_STATE) != 0)
533 					fWorkStatusView->PackageStatusChanged(ref);
534 			}
535 			break;
536 		}
537 
538 		case MSG_RATE_PACKAGE:
539 			_RatePackage();
540 			break;
541 
542 		case MSG_SHOW_SCREENSHOT:
543 			_ShowScreenshot();
544 			break;
545 
546 		case MSG_PACKAGE_WORKER_BUSY:
547 		{
548 			BString reason;
549 			status_t status = message->FindString("reason", &reason);
550 			if (status != B_OK)
551 				break;
552 			if (!fSinglePackageMode)
553 				fWorkStatusView->SetBusy(reason);
554 			break;
555 		}
556 
557 		case MSG_PACKAGE_WORKER_IDLE:
558 			if (!fSinglePackageMode)
559 				fWorkStatusView->SetIdle();
560 			break;
561 
562 		case MSG_ADD_VISIBLE_PACKAGES:
563 		{
564 			struct SemaphoreReleaser {
565 				SemaphoreReleaser(sem_id semaphore)
566 					:
567 					fSemaphore(semaphore)
568 				{ }
569 
570 				~SemaphoreReleaser() { release_sem(fSemaphore); }
571 
572 				sem_id fSemaphore;
573 			};
574 
575 			// Make sure acknowledge semaphore is released even on error,
576 			// so the worker thread won't be blocked
577 			SemaphoreReleaser acknowledger(fShowPackagesAcknowledgeSem);
578 
579 			int32 numPackages = 0;
580 			type_code unused;
581 			status_t status = message->GetInfo("package_ref", &unused,
582 				&numPackages);
583 			if (status != B_OK || numPackages == 0)
584 				break;
585 
586 			int32 listID = 0;
587 			status = message->FindInt32("list_id", &listID);
588 			if (status != B_OK)
589 				break;
590 			if (listID != atomic_get(&fPackagesToShowListID)) {
591 				// list is outdated, ignore
592 				break;
593 			}
594 
595 			for (int i = 0; i < numPackages; i++) {
596 				PackageInfo* packageRaw = NULL;
597 				status = message->FindPointer("package_ref", i,
598 					(void**)&packageRaw);
599 				if (status != B_OK)
600 					break;
601 				PackageInfoRef package(packageRaw, true);
602 
603 				fPackageListView->AddPackage(package);
604 				if (package->IsProminent())
605 					fFeaturedPackagesView->AddPackage(package);
606 			}
607 			break;
608 		}
609 
610 		case MSG_UPDATE_SELECTED_PACKAGE:
611 		{
612 			const PackageInfoRef& selectedPackage = fPackageInfoView->Package();
613 			fFeaturedPackagesView->SelectPackage(selectedPackage, true);
614 			fPackageListView->SelectPackage(selectedPackage);
615 
616 			AutoLocker<BLocker> modelLocker(fModel.Lock());
617 			if (!fVisiblePackages.Contains(fPackageInfoView->Package()))
618 				fPackageInfoView->Clear();
619 			break;
620 		}
621 
622 		default:
623 			BWindow::MessageReceived(message);
624 			break;
625 	}
626 }
627 
628 
629 void
630 MainWindow::StoreSettings(BMessage& settings) const
631 {
632 	settings.AddRect(_WindowFrameName(), Frame());
633 	if (!fSinglePackageMode) {
634 		settings.AddRect("window frame", Frame());
635 
636 		BMessage columnSettings;
637 		fPackageListView->SaveState(&columnSettings);
638 
639 		settings.AddMessage("column settings", &columnSettings);
640 
641 		settings.AddBool("show featured packages",
642 			fModel.ShowFeaturedPackages());
643 		settings.AddBool("show available packages",
644 			fModel.ShowAvailablePackages());
645 		settings.AddBool("show installed packages",
646 			fModel.ShowInstalledPackages());
647 		settings.AddBool("show develop packages", fModel.ShowDevelopPackages());
648 		settings.AddBool("show source packages", fModel.ShowSourcePackages());
649 	}
650 
651 	settings.AddString("username", fModel.Nickname());
652 }
653 
654 
655 void
656 MainWindow::PackageChanged(const PackageInfoEvent& event)
657 {
658 	uint32 watchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE;
659 	if ((event.Changes() & watchedChanges) != 0) {
660 		PackageInfoRef ref(event.Package());
661 		BMessage message(MSG_PACKAGE_CHANGED);
662 		message.AddPointer("package", ref.Get());
663 		message.AddUInt32("changes", event.Changes());
664 		ref.Detach();
665 			// reference needs to be released by MessageReceived();
666 		PostMessage(&message);
667 	}
668 }
669 
670 
671 status_t
672 MainWindow::SchedulePackageActions(PackageActionList& list)
673 {
674 	AutoLocker<BLocker> lock(&fPendingActionsLock);
675 	for (int32 i = 0; i < list.CountItems(); i++) {
676 		if (!fPendingActions.Add(list.ItemAtFast(i)))
677 			return B_NO_MEMORY;
678 	}
679 
680 	return release_sem_etc(fPendingActionsSem, list.CountItems(), 0);
681 }
682 
683 
684 Model*
685 MainWindow::GetModel()
686 {
687 	return &fModel;
688 }
689 
690 
691 void
692 MainWindow::_BuildMenu(BMenuBar* menuBar)
693 {
694 	BMenu* menu = new BMenu(B_TRANSLATE("Tools"));
695 	fRefreshRepositoriesItem = new BMenuItem(
696 		B_TRANSLATE("Refresh repositories"), new BMessage(MSG_REFRESH_REPOS));
697 	menu->AddItem(fRefreshRepositoriesItem);
698 	menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories"
699 		B_UTF8_ELLIPSIS), new BMessage(MSG_MANAGE_REPOS)));
700 	menu->AddItem(new BMenuItem(B_TRANSLATE("Check for updates"
701 		B_UTF8_ELLIPSIS), new BMessage(MSG_SOFTWARE_UPDATER)));
702 
703 	menuBar->AddItem(menu);
704 
705 	fRepositoryMenu = new BMenu(B_TRANSLATE("Repositories"));
706 	menuBar->AddItem(fRepositoryMenu);
707 
708 	menu = new BMenu(B_TRANSLATE("Show"));
709 
710 	fShowAvailablePackagesItem = new BMenuItem(
711 		B_TRANSLATE("Available packages"),
712 		new BMessage(MSG_SHOW_AVAILABLE_PACKAGES));
713 	menu->AddItem(fShowAvailablePackagesItem);
714 
715 	fShowInstalledPackagesItem = new BMenuItem(
716 		B_TRANSLATE("Installed packages"),
717 		new BMessage(MSG_SHOW_INSTALLED_PACKAGES));
718 	menu->AddItem(fShowInstalledPackagesItem);
719 
720 	menu->AddSeparatorItem();
721 
722 	fShowDevelopPackagesItem = new BMenuItem(
723 		B_TRANSLATE("Develop packages"),
724 		new BMessage(MSG_SHOW_DEVELOP_PACKAGES));
725 	menu->AddItem(fShowDevelopPackagesItem);
726 
727 	fShowSourcePackagesItem = new BMenuItem(
728 		B_TRANSLATE("Source packages"),
729 		new BMessage(MSG_SHOW_SOURCE_PACKAGES));
730 	menu->AddItem(fShowSourcePackagesItem);
731 
732 	menuBar->AddItem(menu);
733 }
734 
735 
736 void
737 MainWindow::_BuildUserMenu(BMenuBar* menuBar)
738 {
739 	fUserMenu = new BMenu(B_TRANSLATE("Not logged in"));
740 
741 	fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS),
742 		new BMessage(MSG_LOG_IN));
743 	fUserMenu->AddItem(fLogInItem);
744 
745 	fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"),
746 		new BMessage(MSG_LOG_OUT));
747 	fUserMenu->AddItem(fLogOutItem);
748 
749 	BMenuItem *latestUserUsageConditionsMenuItem =
750 		new BMenuItem(B_TRANSLATE("View latest usage conditions"
751 			B_UTF8_ELLIPSIS),
752 			new BMessage(MSG_VIEW_LATEST_USER_USAGE_CONDITIONS));
753 	fUserMenu->AddItem(latestUserUsageConditionsMenuItem);
754 
755 	fUsersUserUsageConditionsMenuItem =
756 		new BMenuItem(B_TRANSLATE("View agreed usage conditions"
757 			B_UTF8_ELLIPSIS),
758 			new BMessage(MSG_VIEW_USERS_USER_USAGE_CONDITIONS));
759 	fUserMenu->AddItem(fUsersUserUsageConditionsMenuItem);
760 
761 	menuBar->AddItem(fUserMenu);
762 }
763 
764 
765 void
766 MainWindow::_RestoreNickname(const BMessage& settings)
767 {
768 	BString nickname;
769 	if (settings.FindString("username", &nickname) == B_OK
770 		&& nickname.Length() > 0) {
771 		fModel.SetNickname(nickname);
772 	}
773 }
774 
775 
776 const char*
777 MainWindow::_WindowFrameName() const
778 {
779 	if (fSinglePackageMode)
780 		return "small window frame";
781 
782 	return "window frame";
783 }
784 
785 
786 void
787 MainWindow::_RestoreWindowFrame(const BMessage& settings)
788 {
789 	BRect frame = Frame();
790 
791 	BRect windowFrame;
792 	bool fromSettings = false;
793 	if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) {
794 		frame = windowFrame;
795 		fromSettings = true;
796 	} else if (!fSinglePackageMode) {
797 		// Resize to occupy a certain screen size
798 		BRect screenFrame = BScreen(this).Frame();
799 		float width = frame.Width();
800 		float height = frame.Height();
801 		if (width < screenFrame.Width() * .666f
802 			&& height < screenFrame.Height() * .666f) {
803 			frame.bottom = frame.top + screenFrame.Height() * .666f;
804 			frame.right = frame.left
805 				+ std::min(screenFrame.Width() * .666f, height * 7 / 5);
806 		}
807 	}
808 
809 	MoveTo(frame.LeftTop());
810 	ResizeTo(frame.Width(), frame.Height());
811 
812 	if (fromSettings)
813 		MoveOnScreen();
814 	else
815 		CenterOnScreen();
816 }
817 
818 
819 void
820 MainWindow::_InitWorkerThreads()
821 {
822 	fPendingActionsSem = create_sem(0, "PendingPackageActions");
823 	if (fPendingActionsSem >= 0) {
824 		fPendingActionsWorker = spawn_thread(&_PackageActionWorker,
825 			"Planet Express", B_NORMAL_PRIORITY, this);
826 		if (fPendingActionsWorker >= 0)
827 			resume_thread(fPendingActionsWorker);
828 	} else
829 		fPendingActionsWorker = -1;
830 
831 	fPackageToPopulateSem = create_sem(0, "PopulatePackage");
832 	if (fPackageToPopulateSem >= 0) {
833 		fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker,
834 			"Package Populator", B_NORMAL_PRIORITY, this);
835 		if (fPopulatePackageWorker >= 0)
836 			resume_thread(fPopulatePackageWorker);
837 	} else
838 		fPopulatePackageWorker = -1;
839 
840 	fNewPackagesToShowSem = create_sem(0, "ShowPackages");
841 	fShowPackagesAcknowledgeSem = create_sem(0, "ShowPackagesAck");
842 	if (fNewPackagesToShowSem >= 0 && fShowPackagesAcknowledgeSem >= 0) {
843 		fShowPackagesWorker = spawn_thread(&_PackagesToShowWorker,
844 			"Good news everyone", B_NORMAL_PRIORITY, this);
845 		if (fShowPackagesWorker >= 0)
846 			resume_thread(fShowPackagesWorker);
847 	} else
848 		fShowPackagesWorker = -1;
849 }
850 
851 
852 void
853 MainWindow::_AdoptModel()
854 {
855 	{
856 		AutoLocker<BLocker> modelLocker(fModel.Lock());
857 		fVisiblePackages = fModel.CreatePackageList();
858 		AutoLocker<BLocker> listLocker(fPackagesToShowListLock);
859 		fPackagesToShowList = fVisiblePackages;
860 		atomic_add(&fPackagesToShowListID, 1);
861 	}
862 
863 	fFeaturedPackagesView->Clear();
864 	fPackageListView->Clear();
865 
866 	release_sem(fNewPackagesToShowSem);
867 
868 	BAutolock locker(fModel.Lock());
869 	fShowAvailablePackagesItem->SetMarked(fModel.ShowAvailablePackages());
870 	fShowInstalledPackagesItem->SetMarked(fModel.ShowInstalledPackages());
871 	fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages());
872 	fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages());
873 
874 	if (fModel.ShowFeaturedPackages())
875 		fListTabs->Select(0);
876 	else
877 		fListTabs->Select(1);
878 
879 	fFilterView->AdoptModel(fModel);
880 }
881 
882 
883 void
884 MainWindow::_AdoptPackage(const PackageInfoRef& package)
885 {
886 	{
887 		BAutolock locker(fModel.Lock());
888 		fPackageInfoView->SetPackage(package);
889 
890 		if (fFeaturedPackagesView != NULL)
891 			fFeaturedPackagesView->SelectPackage(package);
892 		if (fPackageListView != NULL)
893 			fPackageListView->SelectPackage(package);
894 	}
895 
896 	_PopulatePackageAsync(false);
897 }
898 
899 
900 void
901 MainWindow::_ClearPackage()
902 {
903 	fPackageInfoView->Clear();
904 }
905 
906 
907 void
908 MainWindow::_StopBulkLoad()
909 {
910 	AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock);
911 
912 	if (fBulkLoadProcessCoordinator != NULL) {
913 		printf("will stop full update process coordinator\n");
914 		fBulkLoadProcessCoordinator->Stop();
915 	}
916 }
917 
918 
919 void
920 MainWindow::_StartBulkLoad(bool force)
921 {
922 	AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock);
923 
924 	if (fBulkLoadProcessCoordinator == NULL) {
925 		fBulkLoadProcessCoordinator
926 			= ProcessCoordinatorFactory::CreateBulkLoadCoordinator(
927 				this,
928 					// PackageInfoListener
929 				this,
930 					// ProcessCoordinatorListener
931 				&fModel, force);
932 		fBulkLoadProcessCoordinator->Start();
933 		fRefreshRepositoriesItem->SetEnabled(false);
934 	}
935 }
936 
937 
938 /*! This method is called when there is some change in the bulk load process.
939     A change may mean that a new process has started / stopped etc... or it
940     may mean that the entire coordinator has finished.
941 */
942 
943 void
944 MainWindow::CoordinatorChanged(ProcessCoordinatorState& coordinatorState)
945 {
946 	AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock);
947 
948 	if (fBulkLoadProcessCoordinator == coordinatorState.Coordinator()) {
949 		if (!coordinatorState.IsRunning())
950 			_BulkLoadProcessCoordinatorFinished(coordinatorState);
951 		else {
952 			_NotifyWorkStatusChange(coordinatorState.Message(),
953 				coordinatorState.Progress());
954 				// show the progress to the user.
955 		}
956 	} else {
957 		if (Logger::IsInfoEnabled()) {
958 			printf("unknown process coordinator changed\n");
959 		}
960 	}
961 }
962 
963 
964 void
965 MainWindow::_BulkLoadProcessCoordinatorFinished(
966 	ProcessCoordinatorState& coordinatorState)
967 {
968 	if (coordinatorState.ErrorStatus() != B_OK) {
969 		AppUtils::NotifySimpleError(
970 			B_TRANSLATE("Package update error"),
971 			B_TRANSLATE("While updating package data, a problem has arisen "
972 				"that may cause data to be outdated or missing from the "
973 				"application's display. Additional details regarding this "
974 				"problem may be able to be obtained from the application "
975 				"logs."
976 				ALERT_MSG_LOGS_USER_GUIDE));
977 	}
978 	BMessenger messenger(this);
979 	messenger.SendMessage(MSG_BULK_LOAD_DONE);
980 	// it is safe to delete the coordinator here because it is already known
981 	// that all of the processes have completed and their threads will have
982 	// exited safely by this point.
983 	delete fBulkLoadProcessCoordinator;
984 	fBulkLoadProcessCoordinator = NULL;
985 	fRefreshRepositoriesItem->SetEnabled(true);
986 }
987 
988 
989 void
990 MainWindow::_BulkLoadCompleteReceived()
991 {
992 	_AdoptModel();
993 	_UpdateAvailableRepositories();
994 	fWorkStatusView->SetIdle();
995 }
996 
997 
998 /*! Sends off a message to the Window so that it can change the status view
999     on the front-end in the UI thread.
1000 */
1001 
1002 void
1003 MainWindow::_NotifyWorkStatusChange(const BString& text, float progress)
1004 {
1005 	BMessage message(MSG_WORK_STATUS_CHANGE);
1006 
1007 	if (!text.IsEmpty())
1008 		message.AddString(KEY_WORK_STATUS_TEXT, text);
1009 	message.AddFloat(KEY_WORK_STATUS_PROGRESS, progress);
1010 
1011 	this->PostMessage(&message, this);
1012 }
1013 
1014 
1015 void
1016 MainWindow::_HandleWorkStatusChangeMessageReceived(const BMessage* message)
1017 {
1018 	BString text;
1019 	float progress;
1020 
1021 	if (message->FindString(KEY_WORK_STATUS_TEXT, &text) == B_OK)
1022 		fWorkStatusView->SetText(text);
1023 
1024 	if (message->FindFloat(KEY_WORK_STATUS_PROGRESS, &progress) == B_OK)
1025 		fWorkStatusView->SetProgress(progress);
1026 }
1027 
1028 
1029 status_t
1030 MainWindow::_PackageActionWorker(void* arg)
1031 {
1032 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1033 
1034 	while (acquire_sem(window->fPendingActionsSem) == B_OK) {
1035 		PackageActionRef ref;
1036 		{
1037 			AutoLocker<BLocker> lock(&window->fPendingActionsLock);
1038 			ref = window->fPendingActions.ItemAt(0);
1039 			if (ref.Get() == NULL)
1040 				break;
1041 			window->fPendingActions.Remove(0);
1042 		}
1043 
1044 		BMessenger messenger(window);
1045 		BMessage busyMessage(MSG_PACKAGE_WORKER_BUSY);
1046 		BString text(ref->Label());
1047 		text << B_UTF8_ELLIPSIS;
1048 		busyMessage.AddString("reason", text);
1049 
1050 		messenger.SendMessage(&busyMessage);
1051 		ref->Perform();
1052 		messenger.SendMessage(MSG_PACKAGE_WORKER_IDLE);
1053 	}
1054 
1055 	return 0;
1056 }
1057 
1058 
1059 /*! This method will cause the package to have its data refreshed from
1060     the server application.  The refresh happens in the background; this method
1061     is asynchronous.
1062 */
1063 
1064 void
1065 MainWindow::_PopulatePackageAsync(bool forcePopulate)
1066 {
1067 		// Trigger asynchronous package population from the web-app
1068 	{
1069 		AutoLocker<BLocker> lock(&fPackageToPopulateLock);
1070 		fPackageToPopulate = fPackageInfoView->Package();
1071 		fForcePopulatePackage = forcePopulate;
1072 	}
1073 	release_sem_etc(fPackageToPopulateSem, 1, 0);
1074 
1075 	if (Logger::IsDebugEnabled()) {
1076 		printf("pkg [%s] will be updated from the server.\n",
1077 			fPackageToPopulate->Name().String());
1078 	}
1079 }
1080 
1081 
1082 /*! This method will run in the background.  The thread will block until there
1083     is a package to be updated.  When the thread unblocks, it will update the
1084     package with information from the server.
1085 */
1086 
1087 status_t
1088 MainWindow::_PopulatePackageWorker(void* arg)
1089 {
1090 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1091 
1092 	while (acquire_sem(window->fPackageToPopulateSem) == B_OK) {
1093 		PackageInfoRef package;
1094 		bool force;
1095 		{
1096 			AutoLocker<BLocker> lock(&window->fPackageToPopulateLock);
1097 			package = window->fPackageToPopulate;
1098 			force = window->fForcePopulatePackage;
1099 		}
1100 
1101 		if (package.Get() != NULL) {
1102 			uint32 populateFlags = Model::POPULATE_USER_RATINGS
1103 				| Model::POPULATE_SCREEN_SHOTS
1104 				| Model::POPULATE_CHANGELOG;
1105 
1106 			if (force)
1107 				populateFlags |= Model::POPULATE_FORCE;
1108 
1109 			window->fModel.PopulatePackage(package, populateFlags);
1110 
1111 			if (Logger::IsDebugEnabled()) {
1112 				printf("populating package [%s]\n",
1113 					package->Name().String());
1114 			}
1115 		}
1116 	}
1117 
1118 	return 0;
1119 }
1120 
1121 
1122 /* static */ status_t
1123 MainWindow::_PackagesToShowWorker(void* arg)
1124 {
1125 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1126 
1127 	while (acquire_sem(window->fNewPackagesToShowSem) == B_OK) {
1128 		PackageList packageList;
1129 		int32 listID = 0;
1130 		{
1131 			AutoLocker<BLocker> lock(&window->fPackagesToShowListLock);
1132 			packageList = window->fPackagesToShowList;
1133 			listID = atomic_get(&window->fPackagesToShowListID);
1134 			window->fPackagesToShowList.Clear();
1135 		}
1136 
1137 		// Add packages to list views in batches of kPackagesPerUpdate so we
1138 		// don't block the window thread for long with each iteration
1139 		enum {
1140 			kPackagesPerUpdate = 20
1141 		};
1142 		uint32 packagesInMessage = 0;
1143 		BMessage message(MSG_ADD_VISIBLE_PACKAGES);
1144 		BMessenger messenger(window);
1145 		bool listIsOutdated = false;
1146 
1147 		for (int i = 0; i < packageList.CountItems(); i++) {
1148 			const PackageInfoRef& package = packageList.ItemAtFast(i);
1149 
1150 			if (packagesInMessage >= kPackagesPerUpdate) {
1151 				if (listID != atomic_get(&window->fPackagesToShowListID)) {
1152 					// The model was changed again in the meantime, and thus
1153 					// our package list isn't current anymore. Send no further
1154 					// messags based on this list and go back to start.
1155 					listIsOutdated = true;
1156 					break;
1157 				}
1158 
1159 				message.AddInt32("list_id", listID);
1160 				messenger.SendMessage(&message);
1161 				message.MakeEmpty();
1162 				packagesInMessage = 0;
1163 
1164 				// Don't spam the window's message queue, which would make it
1165 				// unresponsive (i.e. allows UI messages to get in between our
1166 				// messages). When it has processed the message we just sent,
1167 				// it will let us know by releasing the semaphore.
1168 				acquire_sem(window->fShowPackagesAcknowledgeSem);
1169 			}
1170 			package->AcquireReference();
1171 			message.AddPointer("package_ref", package.Get());
1172 			packagesInMessage++;
1173 		}
1174 
1175 		if (listIsOutdated)
1176 			continue;
1177 
1178 		// Send remaining package infos, if any, which didn't make it into
1179 		// the last message (count < kPackagesPerUpdate)
1180 		if (packagesInMessage > 0) {
1181 			message.AddInt32("list_id", listID);
1182 			messenger.SendMessage(&message);
1183 			acquire_sem(window->fShowPackagesAcknowledgeSem);
1184 		}
1185 
1186 		// Update selected package in list views
1187 		messenger.SendMessage(MSG_UPDATE_SELECTED_PACKAGE);
1188 	}
1189 
1190 	return 0;
1191 }
1192 
1193 
1194 void
1195 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage)
1196 {
1197 	UserLoginWindow* window = new UserLoginWindow(this,
1198 		BRect(0, 0, 500, 400), fModel);
1199 
1200 	if (onSuccessMessage.what != 0)
1201 		window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage);
1202 
1203 	window->Show();
1204 }
1205 
1206 
1207 void
1208 MainWindow::_UpdateAuthorization()
1209 {
1210 	BString nickname(fModel.Nickname());
1211 	bool hasUser = !nickname.IsEmpty();
1212 
1213 	if (fLogOutItem != NULL)
1214 		fLogOutItem->SetEnabled(hasUser);
1215 	if (fUsersUserUsageConditionsMenuItem != NULL)
1216 		fUsersUserUsageConditionsMenuItem->SetEnabled(hasUser);
1217 	if (fLogInItem != NULL) {
1218 		if (hasUser)
1219 			fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS));
1220 		else
1221 			fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS));
1222 	}
1223 
1224 	if (fUserMenu != NULL) {
1225 		BString label;
1226 		if (hasUser) {
1227 			label = B_TRANSLATE("Logged in as %User%");
1228 			label.ReplaceAll("%User%", nickname);
1229 		} else {
1230 			label = B_TRANSLATE("Not logged in");
1231 		}
1232 		fUserMenu->Superitem()->SetLabel(label);
1233 	}
1234 }
1235 
1236 
1237 void
1238 MainWindow::_UpdateAvailableRepositories()
1239 {
1240 	fRepositoryMenu->RemoveItems(0, fRepositoryMenu->CountItems(), true);
1241 
1242 	fRepositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All repositories"),
1243 		new BMessage(MSG_DEPOT_SELECTED)));
1244 
1245 	fRepositoryMenu->AddItem(new BSeparatorItem());
1246 
1247 	bool foundSelectedDepot = false;
1248 	const DepotList& depots = fModel.Depots();
1249 	for (int i = 0; i < depots.CountItems(); i++) {
1250 		const DepotInfo& depot = depots.ItemAtFast(i);
1251 
1252 		if (depot.Name().Length() != 0) {
1253 			BMessage* message = new BMessage(MSG_DEPOT_SELECTED);
1254 			message->AddString("name", depot.Name());
1255 			BMenuItem* item = new BMenuItem(depot.Name(), message);
1256 			fRepositoryMenu->AddItem(item);
1257 
1258 			if (depot.Name() == fModel.Depot()) {
1259 				item->SetMarked(true);
1260 				foundSelectedDepot = true;
1261 			}
1262 		}
1263 	}
1264 
1265 	if (!foundSelectedDepot)
1266 		fRepositoryMenu->ItemAt(0)->SetMarked(true);
1267 }
1268 
1269 
1270 bool
1271 MainWindow::_SelectedPackageHasWebAppRepositoryCode()
1272 {
1273 	const PackageInfoRef& package = fPackageInfoView->Package();
1274 	const BString depotName = package->DepotName();
1275 
1276 	if (depotName.IsEmpty()) {
1277 		if (Logger::IsDebugEnabled()) {
1278 			printf("the package [%s] has no depot name\n",
1279 				package->Name().String());
1280 		}
1281 	} else {
1282 		const DepotInfo* depot = fModel.DepotForName(depotName);
1283 
1284 		if (depot == NULL) {
1285 			printf("the depot [%s] was not able to be found\n",
1286 				depotName.String());
1287 		} else {
1288 			BString repositoryCode = depot->WebAppRepositoryCode();
1289 
1290 			if (repositoryCode.IsEmpty()) {
1291 				printf("the depot [%s] has no web app repository code\n",
1292 					depotName.String());
1293 			} else {
1294 				return true;
1295 			}
1296 		}
1297 	}
1298 
1299 	return false;
1300 }
1301 
1302 
1303 void
1304 MainWindow::_RatePackage()
1305 {
1306 	if (!_SelectedPackageHasWebAppRepositoryCode()) {
1307 		BAlert* alert = new(std::nothrow) BAlert(
1308 			B_TRANSLATE("Rating not possible"),
1309 			B_TRANSLATE("This package doesn't seem to be on the HaikuDepot "
1310 				"Server, so it's not possible to create a new rating "
1311 				"or edit an existing rating."),
1312 			B_TRANSLATE("OK"));
1313 		alert->Go();
1314     	return;
1315 	}
1316 
1317 	if (fModel.Nickname().IsEmpty()) {
1318 		BAlert* alert = new(std::nothrow) BAlert(
1319 			B_TRANSLATE("Not logged in"),
1320 			B_TRANSLATE("You need to be logged into an account before you "
1321 				"can rate packages."),
1322 			B_TRANSLATE("Cancel"),
1323 			B_TRANSLATE("Login or Create account"));
1324 
1325 		if (alert == NULL)
1326 			return;
1327 
1328 		int32 choice = alert->Go();
1329 		if (choice == 1)
1330 			_OpenLoginWindow(BMessage(MSG_RATE_PACKAGE));
1331 		return;
1332 	}
1333 
1334 	// TODO: Allow only one RatePackageWindow
1335 	// TODO: Mechanism for remembering the window frame
1336 	RatePackageWindow* window = new RatePackageWindow(this,
1337 		BRect(0, 0, 500, 400), fModel);
1338 	window->SetPackage(fPackageInfoView->Package());
1339 	window->Show();
1340 }
1341 
1342 
1343 void
1344 MainWindow::_ShowScreenshot()
1345 {
1346 	// TODO: Mechanism for remembering the window frame
1347 	if (fScreenshotWindow == NULL)
1348 		fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400));
1349 
1350 	if (fScreenshotWindow->LockWithTimeout(1000) != B_OK)
1351 		return;
1352 
1353 	fScreenshotWindow->SetPackage(fPackageInfoView->Package());
1354 
1355 	if (fScreenshotWindow->IsHidden())
1356 		fScreenshotWindow->Show();
1357 	else
1358 		fScreenshotWindow->Activate();
1359 
1360 	fScreenshotWindow->Unlock();
1361 }
1362 
1363 
1364 void
1365 MainWindow::_ViewUserUsageConditions(
1366 	UserUsageConditionsSelectionMode mode)
1367 {
1368 	UserUsageConditionsWindow* window = new UserUsageConditionsWindow(
1369 		fModel, mode);
1370 	window->Show();
1371 }