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