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