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