xref: /haiku/src/apps/haikudepot/ui/MainWindow.cpp (revision 69f814cded60c5f977a61a2ab747e4456323e6f2)
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 	}
977 	BMessenger messenger(this);
978 	messenger.SendMessage(MSG_BULK_LOAD_DONE);
979 	// it is safe to delete the coordinator here because it is already known
980 	// that all of the processes have completed and their threads will have
981 	// exited safely by this point.
982 	delete fBulkLoadProcessCoordinator;
983 	fBulkLoadProcessCoordinator = NULL;
984 	fRefreshRepositoriesItem->SetEnabled(true);
985 }
986 
987 
988 void
989 MainWindow::_BulkLoadCompleteReceived()
990 {
991 	_AdoptModel();
992 	_UpdateAvailableRepositories();
993 	fWorkStatusView->SetIdle();
994 }
995 
996 
997 /*! Sends off a message to the Window so that it can change the status view
998     on the front-end in the UI thread.
999 */
1000 
1001 void
1002 MainWindow::_NotifyWorkStatusChange(const BString& text, float progress)
1003 {
1004 	BMessage message(MSG_WORK_STATUS_CHANGE);
1005 
1006 	if (!text.IsEmpty())
1007 		message.AddString(KEY_WORK_STATUS_TEXT, text);
1008 	message.AddFloat(KEY_WORK_STATUS_PROGRESS, progress);
1009 
1010 	this->PostMessage(&message, this);
1011 }
1012 
1013 
1014 void
1015 MainWindow::_HandleWorkStatusChangeMessageReceived(const BMessage* message)
1016 {
1017 	BString text;
1018 	float progress;
1019 
1020 	if (message->FindString(KEY_WORK_STATUS_TEXT, &text) == B_OK)
1021 		fWorkStatusView->SetText(text);
1022 
1023 	if (message->FindFloat(KEY_WORK_STATUS_PROGRESS, &progress) == B_OK)
1024 		fWorkStatusView->SetProgress(progress);
1025 }
1026 
1027 
1028 status_t
1029 MainWindow::_PackageActionWorker(void* arg)
1030 {
1031 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1032 
1033 	while (acquire_sem(window->fPendingActionsSem) == B_OK) {
1034 		PackageActionRef ref;
1035 		{
1036 			AutoLocker<BLocker> lock(&window->fPendingActionsLock);
1037 			ref = window->fPendingActions.ItemAt(0);
1038 			if (ref.Get() == NULL)
1039 				break;
1040 			window->fPendingActions.Remove(0);
1041 		}
1042 
1043 		BMessenger messenger(window);
1044 		BMessage busyMessage(MSG_PACKAGE_WORKER_BUSY);
1045 		BString text(ref->Label());
1046 		text << B_UTF8_ELLIPSIS;
1047 		busyMessage.AddString("reason", text);
1048 
1049 		messenger.SendMessage(&busyMessage);
1050 		ref->Perform();
1051 		messenger.SendMessage(MSG_PACKAGE_WORKER_IDLE);
1052 	}
1053 
1054 	return 0;
1055 }
1056 
1057 
1058 /*! This method will cause the package to have its data refreshed from
1059     the server application.  The refresh happens in the background; this method
1060     is asynchronous.
1061 */
1062 
1063 void
1064 MainWindow::_PopulatePackageAsync(bool forcePopulate)
1065 {
1066 		// Trigger asynchronous package population from the web-app
1067 	{
1068 		AutoLocker<BLocker> lock(&fPackageToPopulateLock);
1069 		fPackageToPopulate = fPackageInfoView->Package();
1070 		fForcePopulatePackage = forcePopulate;
1071 	}
1072 	release_sem_etc(fPackageToPopulateSem, 1, 0);
1073 
1074 	if (Logger::IsDebugEnabled()) {
1075 		printf("pkg [%s] will be updated from the server.\n",
1076 			fPackageToPopulate->Name().String());
1077 	}
1078 }
1079 
1080 
1081 /*! This method will run in the background.  The thread will block until there
1082     is a package to be updated.  When the thread unblocks, it will update the
1083     package with information from the server.
1084 */
1085 
1086 status_t
1087 MainWindow::_PopulatePackageWorker(void* arg)
1088 {
1089 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1090 
1091 	while (acquire_sem(window->fPackageToPopulateSem) == B_OK) {
1092 		PackageInfoRef package;
1093 		bool force;
1094 		{
1095 			AutoLocker<BLocker> lock(&window->fPackageToPopulateLock);
1096 			package = window->fPackageToPopulate;
1097 			force = window->fForcePopulatePackage;
1098 		}
1099 
1100 		if (package.Get() != NULL) {
1101 			uint32 populateFlags = Model::POPULATE_USER_RATINGS
1102 				| Model::POPULATE_SCREEN_SHOTS
1103 				| Model::POPULATE_CHANGELOG;
1104 
1105 			if (force)
1106 				populateFlags |= Model::POPULATE_FORCE;
1107 
1108 			window->fModel.PopulatePackage(package, populateFlags);
1109 
1110 			if (Logger::IsDebugEnabled()) {
1111 				printf("populating package [%s]\n",
1112 					package->Name().String());
1113 			}
1114 		}
1115 	}
1116 
1117 	return 0;
1118 }
1119 
1120 
1121 /* static */ status_t
1122 MainWindow::_PackagesToShowWorker(void* arg)
1123 {
1124 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1125 
1126 	while (acquire_sem(window->fNewPackagesToShowSem) == B_OK) {
1127 		PackageList packageList;
1128 		int32 listID = 0;
1129 		{
1130 			AutoLocker<BLocker> lock(&window->fPackagesToShowListLock);
1131 			packageList = window->fPackagesToShowList;
1132 			listID = atomic_get(&window->fPackagesToShowListID);
1133 			window->fPackagesToShowList.Clear();
1134 		}
1135 
1136 		// Add packages to list views in batches of kPackagesPerUpdate so we
1137 		// don't block the window thread for long with each iteration
1138 		enum {
1139 			kPackagesPerUpdate = 20
1140 		};
1141 		uint32 packagesInMessage = 0;
1142 		BMessage message(MSG_ADD_VISIBLE_PACKAGES);
1143 		BMessenger messenger(window);
1144 		bool listIsOutdated = false;
1145 
1146 		for (int i = 0; i < packageList.CountItems(); i++) {
1147 			const PackageInfoRef& package = packageList.ItemAtFast(i);
1148 
1149 			if (packagesInMessage >= kPackagesPerUpdate) {
1150 				if (listID != atomic_get(&window->fPackagesToShowListID)) {
1151 					// The model was changed again in the meantime, and thus
1152 					// our package list isn't current anymore. Send no further
1153 					// messags based on this list and go back to start.
1154 					listIsOutdated = true;
1155 					break;
1156 				}
1157 
1158 				message.AddInt32("list_id", listID);
1159 				messenger.SendMessage(&message);
1160 				message.MakeEmpty();
1161 				packagesInMessage = 0;
1162 
1163 				// Don't spam the window's message queue, which would make it
1164 				// unresponsive (i.e. allows UI messages to get in between our
1165 				// messages). When it has processed the message we just sent,
1166 				// it will let us know by releasing the semaphore.
1167 				acquire_sem(window->fShowPackagesAcknowledgeSem);
1168 			}
1169 			package->AcquireReference();
1170 			message.AddPointer("package_ref", package.Get());
1171 			packagesInMessage++;
1172 		}
1173 
1174 		if (listIsOutdated)
1175 			continue;
1176 
1177 		// Send remaining package infos, if any, which didn't make it into
1178 		// the last message (count < kPackagesPerUpdate)
1179 		if (packagesInMessage > 0) {
1180 			message.AddInt32("list_id", listID);
1181 			messenger.SendMessage(&message);
1182 			acquire_sem(window->fShowPackagesAcknowledgeSem);
1183 		}
1184 
1185 		// Update selected package in list views
1186 		messenger.SendMessage(MSG_UPDATE_SELECTED_PACKAGE);
1187 	}
1188 
1189 	return 0;
1190 }
1191 
1192 
1193 void
1194 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage)
1195 {
1196 	UserLoginWindow* window = new UserLoginWindow(this,
1197 		BRect(0, 0, 500, 400), fModel);
1198 
1199 	if (onSuccessMessage.what != 0)
1200 		window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage);
1201 
1202 	window->Show();
1203 }
1204 
1205 
1206 void
1207 MainWindow::_UpdateAuthorization()
1208 {
1209 	BString nickname(fModel.Nickname());
1210 	bool hasUser = !nickname.IsEmpty();
1211 
1212 	if (fLogOutItem != NULL)
1213 		fLogOutItem->SetEnabled(hasUser);
1214 	if (fUsersUserUsageConditionsMenuItem != NULL)
1215 		fUsersUserUsageConditionsMenuItem->SetEnabled(hasUser);
1216 	if (fLogInItem != NULL) {
1217 		if (hasUser)
1218 			fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS));
1219 		else
1220 			fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS));
1221 	}
1222 
1223 	if (fUserMenu != NULL) {
1224 		BString label;
1225 		if (hasUser) {
1226 			label = B_TRANSLATE("Logged in as %User%");
1227 			label.ReplaceAll("%User%", nickname);
1228 		} else {
1229 			label = B_TRANSLATE("Not logged in");
1230 		}
1231 		fUserMenu->Superitem()->SetLabel(label);
1232 	}
1233 }
1234 
1235 
1236 void
1237 MainWindow::_UpdateAvailableRepositories()
1238 {
1239 	fRepositoryMenu->RemoveItems(0, fRepositoryMenu->CountItems(), true);
1240 
1241 	fRepositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All repositories"),
1242 		new BMessage(MSG_DEPOT_SELECTED)));
1243 
1244 	fRepositoryMenu->AddItem(new BSeparatorItem());
1245 
1246 	bool foundSelectedDepot = false;
1247 	const DepotList& depots = fModel.Depots();
1248 	for (int i = 0; i < depots.CountItems(); i++) {
1249 		const DepotInfo& depot = depots.ItemAtFast(i);
1250 
1251 		if (depot.Name().Length() != 0) {
1252 			BMessage* message = new BMessage(MSG_DEPOT_SELECTED);
1253 			message->AddString("name", depot.Name());
1254 			BMenuItem* item = new BMenuItem(depot.Name(), message);
1255 			fRepositoryMenu->AddItem(item);
1256 
1257 			if (depot.Name() == fModel.Depot()) {
1258 				item->SetMarked(true);
1259 				foundSelectedDepot = true;
1260 			}
1261 		}
1262 	}
1263 
1264 	if (!foundSelectedDepot)
1265 		fRepositoryMenu->ItemAt(0)->SetMarked(true);
1266 }
1267 
1268 
1269 bool
1270 MainWindow::_SelectedPackageHasWebAppRepositoryCode()
1271 {
1272 	const PackageInfoRef& package = fPackageInfoView->Package();
1273 	const BString depotName = package->DepotName();
1274 
1275 	if (depotName.IsEmpty()) {
1276 		if (Logger::IsDebugEnabled()) {
1277 			printf("the package [%s] has no depot name\n",
1278 				package->Name().String());
1279 		}
1280 	} else {
1281 		const DepotInfo* depot = fModel.DepotForName(depotName);
1282 
1283 		if (depot == NULL) {
1284 			printf("the depot [%s] was not able to be found\n",
1285 				depotName.String());
1286 		} else {
1287 			BString repositoryCode = depot->WebAppRepositoryCode();
1288 
1289 			if (repositoryCode.IsEmpty()) {
1290 				printf("the depot [%s] has no web app repository code\n",
1291 					depotName.String());
1292 			} else {
1293 				return true;
1294 			}
1295 		}
1296 	}
1297 
1298 	return false;
1299 }
1300 
1301 
1302 void
1303 MainWindow::_RatePackage()
1304 {
1305 	if (!_SelectedPackageHasWebAppRepositoryCode()) {
1306 		BAlert* alert = new(std::nothrow) BAlert(
1307 			B_TRANSLATE("Rating not possible"),
1308 			B_TRANSLATE("This package doesn't seem to be on the HaikuDepot "
1309 				"Server, so it's not possible to create a new rating "
1310 				"or edit an existing rating."),
1311 			B_TRANSLATE("OK"));
1312 		alert->Go();
1313     	return;
1314 	}
1315 
1316 	if (fModel.Nickname().IsEmpty()) {
1317 		BAlert* alert = new(std::nothrow) BAlert(
1318 			B_TRANSLATE("Not logged in"),
1319 			B_TRANSLATE("You need to be logged into an account before you "
1320 				"can rate packages."),
1321 			B_TRANSLATE("Cancel"),
1322 			B_TRANSLATE("Login or Create account"));
1323 
1324 		if (alert == NULL)
1325 			return;
1326 
1327 		int32 choice = alert->Go();
1328 		if (choice == 1)
1329 			_OpenLoginWindow(BMessage(MSG_RATE_PACKAGE));
1330 		return;
1331 	}
1332 
1333 	// TODO: Allow only one RatePackageWindow
1334 	// TODO: Mechanism for remembering the window frame
1335 	RatePackageWindow* window = new RatePackageWindow(this,
1336 		BRect(0, 0, 500, 400), fModel);
1337 	window->SetPackage(fPackageInfoView->Package());
1338 	window->Show();
1339 }
1340 
1341 
1342 void
1343 MainWindow::_ShowScreenshot()
1344 {
1345 	// TODO: Mechanism for remembering the window frame
1346 	if (fScreenshotWindow == NULL)
1347 		fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400));
1348 
1349 	if (fScreenshotWindow->LockWithTimeout(1000) != B_OK)
1350 		return;
1351 
1352 	fScreenshotWindow->SetPackage(fPackageInfoView->Package());
1353 
1354 	if (fScreenshotWindow->IsHidden())
1355 		fScreenshotWindow->Show();
1356 	else
1357 		fScreenshotWindow->Activate();
1358 
1359 	fScreenshotWindow->Unlock();
1360 }
1361 
1362 
1363 void
1364 MainWindow::_ViewUserUsageConditions(
1365 	UserUsageConditionsSelectionMode mode)
1366 {
1367 	UserUsageConditionsWindow* window = new UserUsageConditionsWindow(
1368 		fModel, mode);
1369 	window->Show();
1370 }