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