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