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 PackageFilterModel* filterModel = fModel.PackageFilter(); 485 filterModel->SetShowAvailablePackages(!filterModel->ShowAvailablePackages()); 486 } 487 _AdoptModel(); 488 break; 489 490 case MSG_SHOW_INSTALLED_PACKAGES: 491 { 492 BAutolock locker(fModel.Lock()); 493 PackageFilterModel* filterModel = fModel.PackageFilter(); 494 filterModel->SetShowInstalledPackages(!filterModel->ShowInstalledPackages()); 495 } 496 _AdoptModel(); 497 break; 498 499 case MSG_SHOW_SOURCE_PACKAGES: 500 { 501 BAutolock locker(fModel.Lock()); 502 PackageFilterModel* filterModel = fModel.PackageFilter(); 503 filterModel->SetShowSourcePackages(!filterModel->ShowSourcePackages()); 504 } 505 _AdoptModel(); 506 break; 507 508 case MSG_SHOW_DEVELOP_PACKAGES: 509 { 510 BAutolock locker(fModel.Lock()); 511 PackageFilterModel* filterModel = fModel.PackageFilter(); 512 filterModel->SetShowDevelopPackages(!filterModel->ShowDevelopPackages()); 513 } 514 _AdoptModel(); 515 break; 516 517 // this may be triggered by, for example, a user rating being added 518 // or having been altered. 519 case MSG_SERVER_DATA_CHANGED: 520 { 521 BString name; 522 if (message->FindString("name", &name) == B_OK) { 523 BAutolock locker(fModel.Lock()); 524 if (fPackageInfoView->Package()->Name() == name) { 525 _PopulatePackageAsync(true); 526 } else { 527 HDDEBUG("pkg [%s] is updated on the server, but is " 528 "not selected so will not be updated.", 529 name.String()); 530 } 531 } 532 break; 533 } 534 535 case MSG_INCREMENT_VIEW_COUNTER: 536 _HandleIncrementViewCounter(message); 537 break; 538 539 case MSG_PACKAGE_SELECTED: 540 { 541 BString name; 542 if (message->FindString("name", &name) == B_OK) { 543 PackageInfoRef package; 544 { 545 BAutolock locker(fModel.Lock()); 546 package = fModel.PackageForName(name); 547 } 548 if (!package.IsSet() || name != package->Name()) 549 debugger("unable to find the named package"); 550 else { 551 _AdoptPackage(package); 552 _SetupDelayedIncrementViewCounter(package); 553 } 554 } else { 555 _ClearPackage(); 556 } 557 break; 558 } 559 560 case MSG_CATEGORY_SELECTED: 561 { 562 BString code; 563 if (message->FindString("code", &code) != B_OK) 564 code = ""; 565 { 566 BAutolock locker(fModel.Lock()); 567 fModel.PackageFilter()->SetCategory(code); 568 } 569 _AdoptModel(); 570 break; 571 } 572 573 case MSG_DEPOT_SELECTED: 574 { 575 BString name; 576 if (message->FindString("name", &name) != B_OK) 577 name = ""; 578 { 579 BAutolock locker(fModel.Lock()); 580 fModel.PackageFilter()->SetDepotName(name); 581 } 582 _AdoptModel(); 583 _UpdateAvailableRepositories(); 584 break; 585 } 586 587 case MSG_SEARCH_TERMS_MODIFIED: 588 { 589 // TODO: Do this with a delay! 590 BString searchTerms; 591 if (message->FindString("search terms", &searchTerms) != B_OK) 592 searchTerms = ""; 593 { 594 BAutolock locker(fModel.Lock()); 595 fModel.PackageFilter()->SetSearchTerms(searchTerms); 596 } 597 _AdoptModel(); 598 break; 599 } 600 601 case MSG_PACKAGE_CHANGED: 602 { 603 PackageInfo* info; 604 if (message->FindPointer("package", (void**)&info) == B_OK) { 605 PackageInfoRef ref(info, true); 606 fFeaturedPackagesView->BeginAddRemove(); 607 _AddRemovePackageFromLists(ref); 608 fFeaturedPackagesView->EndAddRemove(); 609 } 610 break; 611 } 612 613 case MSG_PROCESS_COORDINATOR_CHANGED: 614 { 615 ProcessCoordinatorState state(message); 616 _HandleProcessCoordinatorChanged(state); 617 break; 618 } 619 620 case MSG_RATE_PACKAGE: 621 _RatePackage(); 622 break; 623 624 case MSG_SHOW_SCREENSHOT: 625 _ShowScreenshot(); 626 break; 627 628 case MSG_PACKAGE_WORKER_BUSY: 629 { 630 BString reason; 631 status_t status = message->FindString("reason", &reason); 632 if (status != B_OK) 633 break; 634 fWorkStatusView->SetBusy(reason); 635 break; 636 } 637 638 case MSG_PACKAGE_WORKER_IDLE: 639 fWorkStatusView->SetIdle(); 640 break; 641 642 case MSG_USER_USAGE_CONDITIONS_NOT_LATEST: 643 { 644 BMessage userDetailMsg; 645 if (message->FindMessage("userDetail", &userDetailMsg) != B_OK) { 646 debugger("expected the [userDetail] data to be carried in the " 647 "message."); 648 } 649 UserDetail userDetail(&userDetailMsg); 650 _HandleUserUsageConditionsNotLatest(userDetail); 651 break; 652 } 653 654 default: 655 BWindow::MessageReceived(message); 656 break; 657 } 658 } 659 660 661 static const char* 662 main_window_package_list_view_mode_str(package_list_view_mode mode) 663 { 664 if (mode == PROMINENT) 665 return "PROMINENT"; 666 return "ALL"; 667 } 668 669 670 static package_list_view_mode 671 main_window_str_to_package_list_view_mode(const BString& str) 672 { 673 if (str == "PROMINENT") 674 return PROMINENT; 675 return ALL; 676 } 677 678 679 void 680 MainWindow::StoreSettings(BMessage& settings) 681 { 682 settings.AddRect(_WindowFrameName(), Frame()); 683 if (!fSinglePackageMode) { 684 settings.AddRect("window frame", Frame()); 685 686 BMessage columnSettings; 687 if (fPackageListView != NULL) 688 fPackageListView->SaveState(&columnSettings); 689 690 settings.AddMessage("column settings", &columnSettings); 691 692 settings.AddString(SETTING_PACKAGE_LIST_VIEW_MODE, 693 main_window_package_list_view_mode_str( 694 fModel.PackageListViewMode())); 695 696 settings.AddBool(SETTING_SHOW_AVAILABLE_PACKAGES, 697 fModel.PackageFilter()->ShowAvailablePackages()); 698 settings.AddBool(SETTING_SHOW_INSTALLED_PACKAGES, 699 fModel.PackageFilter()->ShowInstalledPackages()); 700 settings.AddBool(SETTING_SHOW_DEVELOP_PACKAGES, 701 fModel.PackageFilter()->ShowDevelopPackages()); 702 settings.AddBool(SETTING_SHOW_SOURCE_PACKAGES, 703 fModel.PackageFilter()->ShowSourcePackages()); 704 705 settings.AddBool(SETTING_CAN_SHARE_ANONYMOUS_USER_DATA, 706 fModel.CanShareAnonymousUsageData()); 707 } 708 709 settings.AddString("username", fModel.Nickname()); 710 } 711 712 713 void 714 MainWindow::Consume(ProcessCoordinator *item) 715 { 716 _AddProcessCoordinator(item); 717 } 718 719 720 void 721 MainWindow::PackageChanged(const PackageInfoEvent& event) 722 { 723 uint32 watchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE; 724 if ((event.Changes() & watchedChanges) != 0) { 725 PackageInfoRef ref(event.Package()); 726 BMessage message(MSG_PACKAGE_CHANGED); 727 message.AddPointer("package", ref.Get()); 728 ref.Detach(); 729 // reference needs to be released by MessageReceived(); 730 PostMessage(&message); 731 } 732 } 733 734 735 void 736 MainWindow::_BuildMenu(BMenuBar* menuBar) 737 { 738 BMenu* menu = new BMenu(B_TRANSLATE_SYSTEM_NAME("HaikuDepot")); 739 fRefreshRepositoriesItem = new BMenuItem( 740 B_TRANSLATE("Refresh repositories"), new BMessage(MSG_REFRESH_REPOS)); 741 menu->AddItem(fRefreshRepositoriesItem); 742 menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories" 743 B_UTF8_ELLIPSIS), new BMessage(MSG_MANAGE_REPOS))); 744 menu->AddItem(new BMenuItem(B_TRANSLATE("Check for updates" 745 B_UTF8_ELLIPSIS), new BMessage(MSG_SOFTWARE_UPDATER))); 746 menu->AddSeparatorItem(); 747 menu->AddItem(new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS), 748 new BMessage(MSG_SETTINGS), ',')); 749 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 750 new BMessage(B_QUIT_REQUESTED), 'Q')); 751 menuBar->AddItem(menu); 752 753 fRepositoryMenu = new BMenu(B_TRANSLATE("Repositories")); 754 menuBar->AddItem(fRepositoryMenu); 755 756 menu = new BMenu(B_TRANSLATE("Show")); 757 758 fShowAvailablePackagesItem = new BMenuItem( 759 B_TRANSLATE("Available packages"), 760 new BMessage(MSG_SHOW_AVAILABLE_PACKAGES)); 761 menu->AddItem(fShowAvailablePackagesItem); 762 763 fShowInstalledPackagesItem = new BMenuItem( 764 B_TRANSLATE("Installed packages"), 765 new BMessage(MSG_SHOW_INSTALLED_PACKAGES)); 766 menu->AddItem(fShowInstalledPackagesItem); 767 768 menu->AddSeparatorItem(); 769 770 fShowDevelopPackagesItem = new BMenuItem( 771 B_TRANSLATE("Develop packages"), 772 new BMessage(MSG_SHOW_DEVELOP_PACKAGES)); 773 menu->AddItem(fShowDevelopPackagesItem); 774 775 fShowSourcePackagesItem = new BMenuItem( 776 B_TRANSLATE("Source packages"), 777 new BMessage(MSG_SHOW_SOURCE_PACKAGES)); 778 menu->AddItem(fShowSourcePackagesItem); 779 780 menuBar->AddItem(menu); 781 } 782 783 784 void 785 MainWindow::_BuildUserMenu(BMenuBar* menuBar) 786 { 787 fUserMenu = new BMenu(B_TRANSLATE("Not logged in")); 788 789 fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS), 790 new BMessage(MSG_LOG_IN)); 791 fUserMenu->AddItem(fLogInItem); 792 793 fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"), 794 new BMessage(MSG_LOG_OUT)); 795 fUserMenu->AddItem(fLogOutItem); 796 797 BMenuItem *latestUserUsageConditionsMenuItem = 798 new BMenuItem(B_TRANSLATE("View latest usage conditions" 799 B_UTF8_ELLIPSIS), 800 new BMessage(MSG_VIEW_LATEST_USER_USAGE_CONDITIONS)); 801 fUserMenu->AddItem(latestUserUsageConditionsMenuItem); 802 803 fUsersUserUsageConditionsMenuItem = 804 new BMenuItem(B_TRANSLATE("View agreed usage conditions" 805 B_UTF8_ELLIPSIS), 806 new BMessage(MSG_VIEW_USERS_USER_USAGE_CONDITIONS)); 807 fUserMenu->AddItem(fUsersUserUsageConditionsMenuItem); 808 809 menuBar->AddItem(fUserMenu); 810 } 811 812 813 void 814 MainWindow::_RestoreNickname(const BMessage& settings) 815 { 816 BString nickname; 817 if (settings.FindString("username", &nickname) == B_OK 818 && nickname.Length() > 0) { 819 fModel.SetNickname(nickname); 820 } 821 } 822 823 824 const char* 825 MainWindow::_WindowFrameName() const 826 { 827 if (fSinglePackageMode) 828 return "small window frame"; 829 830 return "window frame"; 831 } 832 833 834 void 835 MainWindow::_RestoreWindowFrame(const BMessage& settings) 836 { 837 BRect frame = Frame(); 838 839 BRect windowFrame; 840 bool fromSettings = false; 841 if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) { 842 frame = windowFrame; 843 fromSettings = true; 844 } else if (!fSinglePackageMode) { 845 // Resize to occupy a certain screen size 846 BRect screenFrame = BScreen(this).Frame(); 847 float width = frame.Width(); 848 float height = frame.Height(); 849 if (width < screenFrame.Width() * .666f 850 && height < screenFrame.Height() * .666f) { 851 frame.bottom = frame.top + screenFrame.Height() * .666f; 852 frame.right = frame.left 853 + std::min(screenFrame.Width() * .666f, height * 7 / 5); 854 } 855 } 856 857 MoveTo(frame.LeftTop()); 858 ResizeTo(frame.Width(), frame.Height()); 859 860 if (fromSettings) 861 MoveOnScreen(); 862 else 863 CenterOnScreen(); 864 } 865 866 867 void 868 MainWindow::_RestoreModelSettings(const BMessage& settings) 869 { 870 BString packageListViewMode; 871 if (settings.FindString(SETTING_PACKAGE_LIST_VIEW_MODE, 872 &packageListViewMode) == B_OK) { 873 fModel.SetPackageListViewMode( 874 main_window_str_to_package_list_view_mode(packageListViewMode)); 875 } 876 877 bool showOption; 878 879 if (settings.FindBool(SETTING_SHOW_AVAILABLE_PACKAGES, &showOption) == B_OK) 880 fModel.PackageFilter()->SetShowAvailablePackages(showOption); 881 if (settings.FindBool(SETTING_SHOW_INSTALLED_PACKAGES, &showOption) == B_OK) 882 fModel.PackageFilter()->SetShowInstalledPackages(showOption); 883 if (settings.FindBool(SETTING_SHOW_DEVELOP_PACKAGES, &showOption) == B_OK) 884 fModel.PackageFilter()->SetShowDevelopPackages(showOption); 885 if (settings.FindBool(SETTING_SHOW_SOURCE_PACKAGES, &showOption) == B_OK) 886 fModel.PackageFilter()->SetShowSourcePackages(showOption); 887 888 if (settings.FindBool(SETTING_CAN_SHARE_ANONYMOUS_USER_DATA, 889 &showOption) == B_OK) { 890 fModel.SetCanShareAnonymousUsageData(showOption); 891 } 892 } 893 894 895 void 896 MainWindow::_MaybePromptCanShareAnonymousUserData(const BMessage& settings) 897 { 898 bool showOption; 899 if (settings.FindBool(SETTING_CAN_SHARE_ANONYMOUS_USER_DATA, 900 &showOption) == B_NAME_NOT_FOUND) { 901 _PromptCanShareAnonymousUserData(); 902 } 903 } 904 905 906 void 907 MainWindow::_PromptCanShareAnonymousUserData() 908 { 909 BAlert* alert = new(std::nothrow) BAlert( 910 B_TRANSLATE("Sending anonymous usage data"), 911 B_TRANSLATE("Would it be acceptable to send anonymous usage data to the" 912 " HaikuDepotServer system from this computer? You can change your" 913 " preference in the \"Settings\" window later."), 914 B_TRANSLATE("No"), 915 B_TRANSLATE("Yes")); 916 917 int32 result = alert->Go(); 918 fModel.SetCanShareAnonymousUsageData(1 == result); 919 } 920 921 922 void 923 MainWindow::_InitWorkerThreads() 924 { 925 fPackageToPopulateSem = create_sem(0, "PopulatePackage"); 926 if (fPackageToPopulateSem >= 0) { 927 fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker, 928 "Package Populator", B_NORMAL_PRIORITY, this); 929 if (fPopulatePackageWorker >= 0) 930 resume_thread(fPopulatePackageWorker); 931 } else 932 fPopulatePackageWorker = -1; 933 } 934 935 936 void 937 MainWindow::_AdoptModelControls() 938 { 939 if (fSinglePackageMode) 940 return; 941 942 BAutolock locker(fModel.Lock()); 943 fShowAvailablePackagesItem->SetMarked(fModel.PackageFilter()->ShowAvailablePackages()); 944 fShowInstalledPackagesItem->SetMarked(fModel.PackageFilter()->ShowInstalledPackages()); 945 fShowSourcePackagesItem->SetMarked(fModel.PackageFilter()->ShowSourcePackages()); 946 fShowDevelopPackagesItem->SetMarked(fModel.PackageFilter()->ShowDevelopPackages()); 947 948 if (fModel.PackageListViewMode() == PROMINENT) 949 fListTabs->Select(TAB_PROMINENT_PACKAGES); 950 else 951 fListTabs->Select(TAB_ALL_PACKAGES); 952 953 fFilterView->AdoptModel(fModel); 954 } 955 956 957 void 958 MainWindow::_AdoptModel() 959 { 960 HDTRACE("adopting model to main window ui"); 961 962 if (fSinglePackageMode) 963 return; 964 965 std::vector<DepotInfoRef> depots = _CreateSnapshotOfDepots(); 966 std::vector<DepotInfoRef>::iterator it; 967 968 fFeaturedPackagesView->BeginAddRemove(); 969 970 for (it = depots.begin(); it != depots.end(); it++) { 971 DepotInfoRef depotInfoRef = *it; 972 for (int i = 0; i < depotInfoRef->CountPackages(); i++) { 973 PackageInfoRef package = depotInfoRef->PackageAtIndex(i); 974 _AddRemovePackageFromLists(package); 975 } 976 } 977 978 fFeaturedPackagesView->EndAddRemove(); 979 980 _AdoptModelControls(); 981 } 982 983 984 void 985 MainWindow::_AddRemovePackageFromLists(const PackageInfoRef& package) 986 { 987 bool matches; 988 989 { 990 AutoLocker<BLocker> modelLocker(fModel.Lock()); 991 matches = fModel.PackageFilter()->Filter()->AcceptsPackage(package); 992 } 993 994 if (matches) { 995 if (package->IsProminent()) 996 fFeaturedPackagesView->AddPackage(package); 997 fPackageListView->AddPackage(package); 998 } else { 999 fFeaturedPackagesView->RemovePackage(package); 1000 fPackageListView->RemovePackage(package); 1001 } 1002 } 1003 1004 1005 void 1006 MainWindow::_SetupDelayedIncrementViewCounter(const PackageInfoRef package) { 1007 if (fIncrementViewCounterDelayedRunner != NULL) { 1008 fIncrementViewCounterDelayedRunner->SetCount(0); 1009 delete fIncrementViewCounterDelayedRunner; 1010 } 1011 BMessage message(MSG_INCREMENT_VIEW_COUNTER); 1012 message.SetString("name", package->Name()); 1013 fIncrementViewCounterDelayedRunner = 1014 new BMessageRunner(BMessenger(this), &message, 1015 kIncrementViewCounterDelayMicros, 1); 1016 if (fIncrementViewCounterDelayedRunner->InitCheck() 1017 != B_OK) { 1018 HDERROR("unable to init the increment view counter"); 1019 } 1020 } 1021 1022 1023 void 1024 MainWindow::_HandleIncrementViewCounter(const BMessage* message) 1025 { 1026 BString name; 1027 if (message->FindString("name", &name) == B_OK) { 1028 const PackageInfoRef& viewedPackage = 1029 fPackageInfoView->Package(); 1030 if (viewedPackage.IsSet()) { 1031 if (viewedPackage->Name() == name) 1032 _IncrementViewCounter(viewedPackage); 1033 else 1034 HDINFO("incr. view counter; name mismatch"); 1035 } else 1036 HDINFO("incr. view counter; no viewed pkg"); 1037 } else 1038 HDERROR("incr. view counter; no name"); 1039 fIncrementViewCounterDelayedRunner->SetCount(0); 1040 delete fIncrementViewCounterDelayedRunner; 1041 fIncrementViewCounterDelayedRunner = NULL; 1042 } 1043 1044 1045 void 1046 MainWindow::_IncrementViewCounter(const PackageInfoRef package) 1047 { 1048 bool shouldIncrementViewCounter = false; 1049 1050 { 1051 AutoLocker<BLocker> modelLocker(fModel.Lock()); 1052 bool canShareAnonymousUsageData = fModel.CanShareAnonymousUsageData(); 1053 if (canShareAnonymousUsageData && !package->Viewed()) { 1054 package->SetViewed(); 1055 shouldIncrementViewCounter = true; 1056 } 1057 } 1058 1059 if (shouldIncrementViewCounter) { 1060 ProcessCoordinator* incrementViewCoordinator = 1061 ProcessCoordinatorFactory::CreateIncrementViewCounter( 1062 &fModel, package); 1063 _AddProcessCoordinator(incrementViewCoordinator); 1064 } 1065 } 1066 1067 1068 void 1069 MainWindow::_AdoptPackage(const PackageInfoRef& package) 1070 { 1071 { 1072 BAutolock locker(fModel.Lock()); 1073 fPackageInfoView->SetPackage(package); 1074 1075 if (fFeaturedPackagesView != NULL) 1076 fFeaturedPackagesView->SelectPackage(package); 1077 if (fPackageListView != NULL) 1078 fPackageListView->SelectPackage(package); 1079 } 1080 1081 _PopulatePackageAsync(false); 1082 } 1083 1084 1085 void 1086 MainWindow::_ClearPackage() 1087 { 1088 fPackageInfoView->Clear(); 1089 } 1090 1091 1092 void 1093 MainWindow::_StartBulkLoad(bool force) 1094 { 1095 if (fFeaturedPackagesView != NULL) 1096 fFeaturedPackagesView->Clear(); 1097 if (fPackageListView != NULL) 1098 fPackageListView->Clear(); 1099 fPackageInfoView->Clear(); 1100 1101 fRefreshRepositoriesItem->SetEnabled(false); 1102 ProcessCoordinator* bulkLoadCoordinator = 1103 ProcessCoordinatorFactory::CreateBulkLoadCoordinator( 1104 fPackageInfoListener, &fModel, force); 1105 _AddProcessCoordinator(bulkLoadCoordinator); 1106 } 1107 1108 1109 void 1110 MainWindow::_BulkLoadCompleteReceived(status_t errorStatus) 1111 { 1112 if (errorStatus != B_OK) { 1113 AppUtils::NotifySimpleError( 1114 B_TRANSLATE("Package update error"), 1115 B_TRANSLATE("While updating package data, a problem has arisen " 1116 "that may cause data to be outdated or missing from the " 1117 "application's display. Additional details regarding this " 1118 "problem may be able to be obtained from the application " 1119 "logs." 1120 ALERT_MSG_LOGS_USER_GUIDE)); 1121 } 1122 1123 fRefreshRepositoriesItem->SetEnabled(true); 1124 _AdoptModel(); 1125 _UpdateAvailableRepositories(); 1126 1127 // if after loading everything in, it transpires that there are no 1128 // featured packages then the featured packages should be disabled 1129 // and the user should be switched to the "all packages" view so 1130 // that they are not presented with a blank window! 1131 1132 bool hasProminentPackages = fModel.HasAnyProminentPackages(); 1133 fListTabs->TabAt(TAB_PROMINENT_PACKAGES)->SetEnabled(hasProminentPackages); 1134 if (!hasProminentPackages 1135 && fListTabs->Selection() == TAB_PROMINENT_PACKAGES) { 1136 fModel.SetPackageListViewMode(ALL); 1137 fListTabs->Select(TAB_ALL_PACKAGES); 1138 } 1139 } 1140 1141 1142 void 1143 MainWindow::_NotifyWorkStatusClear() 1144 { 1145 BMessage message(MSG_WORK_STATUS_CLEAR); 1146 this->PostMessage(&message, this); 1147 } 1148 1149 1150 void 1151 MainWindow::_HandleWorkStatusClear() 1152 { 1153 fWorkStatusView->SetText(""); 1154 fWorkStatusView->SetIdle(); 1155 } 1156 1157 1158 /*! Sends off a message to the Window so that it can change the status view 1159 on the front-end in the UI thread. 1160 */ 1161 1162 void 1163 MainWindow::_NotifyWorkStatusChange(const BString& text, float progress) 1164 { 1165 BMessage message(MSG_WORK_STATUS_CHANGE); 1166 1167 if (!text.IsEmpty()) 1168 message.AddString(KEY_WORK_STATUS_TEXT, text); 1169 message.AddFloat(KEY_WORK_STATUS_PROGRESS, progress); 1170 1171 this->PostMessage(&message, this); 1172 } 1173 1174 1175 void 1176 MainWindow::_HandleExternalPackageUpdateMessageReceived(const BMessage* message) 1177 { 1178 BStringList addedPackageNames; 1179 BStringList removedPackageNames; 1180 1181 if (message->FindStrings("added package names", 1182 &addedPackageNames) == B_OK) { 1183 addedPackageNames.Sort(); 1184 AutoLocker<BLocker> locker(fModel.Lock()); 1185 fModel.SetStateForPackagesByName(addedPackageNames, ACTIVATED); 1186 } 1187 else 1188 HDINFO("no [added package names] key in inbound message"); 1189 1190 if (message->FindStrings("removed package names", 1191 &removedPackageNames) == B_OK) { 1192 removedPackageNames.Sort(); 1193 AutoLocker<BLocker> locker(fModel.Lock()); 1194 fModel.SetStateForPackagesByName(addedPackageNames, UNINSTALLED); 1195 } else 1196 HDINFO("no [removed package names] key in inbound message"); 1197 } 1198 1199 1200 void 1201 MainWindow::_HandleWorkStatusChangeMessageReceived(const BMessage* message) 1202 { 1203 if (fWorkStatusView == NULL) 1204 return; 1205 1206 BString text; 1207 float progress; 1208 1209 if (message->FindString(KEY_WORK_STATUS_TEXT, &text) == B_OK) 1210 fWorkStatusView->SetText(text); 1211 1212 if (message->FindFloat(KEY_WORK_STATUS_PROGRESS, &progress) == B_OK) { 1213 if (progress < 0.0f) 1214 fWorkStatusView->SetBusy(); 1215 else 1216 fWorkStatusView->SetProgress(progress); 1217 } else { 1218 HDERROR("work status change missing progress on update message"); 1219 fWorkStatusView->SetProgress(0.0f); 1220 } 1221 } 1222 1223 1224 /*! This method will cause the package to have its data refreshed from 1225 the server application. The refresh happens in the background; this method 1226 is asynchronous. 1227 */ 1228 1229 void 1230 MainWindow::_PopulatePackageAsync(bool forcePopulate) 1231 { 1232 // Trigger asynchronous package population from the web-app 1233 { 1234 AutoLocker<BLocker> lock(&fPackageToPopulateLock); 1235 fPackageToPopulate = fPackageInfoView->Package(); 1236 fForcePopulatePackage = forcePopulate; 1237 } 1238 release_sem_etc(fPackageToPopulateSem, 1, 0); 1239 1240 HDDEBUG("pkg [%s] will be updated from the server.", 1241 fPackageToPopulate->Name().String()); 1242 } 1243 1244 1245 /*! This method will run in the background. The thread will block until there 1246 is a package to be updated. When the thread unblocks, it will update the 1247 package with information from the server. 1248 */ 1249 1250 status_t 1251 MainWindow::_PopulatePackageWorker(void* arg) 1252 { 1253 MainWindow* window = reinterpret_cast<MainWindow*>(arg); 1254 1255 while (acquire_sem(window->fPackageToPopulateSem) == B_OK) { 1256 PackageInfoRef package; 1257 bool force; 1258 { 1259 AutoLocker<BLocker> lock(&window->fPackageToPopulateLock); 1260 package = window->fPackageToPopulate; 1261 force = window->fForcePopulatePackage; 1262 } 1263 1264 if (package.IsSet()) { 1265 uint32 populateFlags = Model::POPULATE_USER_RATINGS 1266 | Model::POPULATE_CHANGELOG; 1267 1268 if (force) 1269 populateFlags |= Model::POPULATE_FORCE; 1270 1271 window->fModel.PopulatePackage(package, populateFlags); 1272 1273 HDDEBUG("populating package [%s]", package->Name().String()); 1274 } 1275 } 1276 1277 return 0; 1278 } 1279 1280 1281 void 1282 MainWindow::_OpenSettingsWindow() 1283 { 1284 SettingsWindow* window = new SettingsWindow(this, &fModel); 1285 window->Show(); 1286 } 1287 1288 1289 void 1290 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage) 1291 { 1292 UserLoginWindow* window = new UserLoginWindow(this, 1293 BRect(0, 0, 500, 400), fModel); 1294 1295 if (onSuccessMessage.what != 0) 1296 window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage); 1297 1298 window->Show(); 1299 } 1300 1301 1302 void 1303 MainWindow::_StartUserVerify() 1304 { 1305 if (!fModel.Nickname().IsEmpty()) { 1306 _AddProcessCoordinator( 1307 ProcessCoordinatorFactory::CreateUserDetailVerifierCoordinator( 1308 this, 1309 // UserDetailVerifierListener 1310 &fModel) ); 1311 } 1312 } 1313 1314 1315 void 1316 MainWindow::_UpdateAuthorization() 1317 { 1318 BString nickname(fModel.Nickname()); 1319 bool hasUser = !nickname.IsEmpty(); 1320 1321 if (fLogOutItem != NULL) 1322 fLogOutItem->SetEnabled(hasUser); 1323 if (fUsersUserUsageConditionsMenuItem != NULL) 1324 fUsersUserUsageConditionsMenuItem->SetEnabled(hasUser); 1325 if (fLogInItem != NULL) { 1326 if (hasUser) 1327 fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS)); 1328 else 1329 fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS)); 1330 } 1331 1332 if (fUserMenu != NULL) { 1333 BString label; 1334 if (hasUser) { 1335 label = B_TRANSLATE("Logged in as %User%"); 1336 label.ReplaceAll("%User%", nickname); 1337 } else { 1338 label = B_TRANSLATE("Not logged in"); 1339 } 1340 fUserMenu->Superitem()->SetLabel(label); 1341 } 1342 } 1343 1344 1345 void 1346 MainWindow::_UpdateAvailableRepositories() 1347 { 1348 fRepositoryMenu->RemoveItems(0, fRepositoryMenu->CountItems(), true); 1349 1350 fRepositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All repositories"), 1351 new BMessage(MSG_DEPOT_SELECTED))); 1352 1353 fRepositoryMenu->AddItem(new BSeparatorItem()); 1354 1355 bool foundSelectedDepot = false; 1356 std::vector<DepotInfoRef> depots = _CreateSnapshotOfDepots(); 1357 std::vector<DepotInfoRef>::iterator it; 1358 1359 for (it = depots.begin(); it != depots.end(); it++) { 1360 DepotInfoRef depot = *it; 1361 1362 if (depot->Name().Length() != 0) { 1363 BMessage* message = new BMessage(MSG_DEPOT_SELECTED); 1364 message->AddString("name", depot->Name()); 1365 BMenuItem* item = new(std::nothrow) BMenuItem(depot->Name(), message); 1366 1367 if (item == NULL) 1368 HDFATAL("memory exhaustion"); 1369 1370 fRepositoryMenu->AddItem(item); 1371 1372 if (depot->Name() == fModel.PackageFilter()->DepotName()) { 1373 item->SetMarked(true); 1374 foundSelectedDepot = true; 1375 } 1376 } 1377 } 1378 1379 if (!foundSelectedDepot) 1380 fRepositoryMenu->ItemAt(0)->SetMarked(true); 1381 } 1382 1383 1384 bool 1385 MainWindow::_SelectedPackageHasWebAppRepositoryCode() 1386 { 1387 const PackageInfoRef& package = fPackageInfoView->Package(); 1388 const BString depotName = package->DepotName(); 1389 1390 if (depotName.IsEmpty()) { 1391 HDDEBUG("the package [%s] has no depot name", package->Name().String()); 1392 } else { 1393 const DepotInfo* depot = fModel.DepotForName(depotName); 1394 1395 if (depot == NULL) { 1396 HDINFO("the depot [%s] was not able to be found", 1397 depotName.String()); 1398 } else { 1399 BString repositoryCode = depot->WebAppRepositoryCode(); 1400 1401 if (repositoryCode.IsEmpty()) { 1402 HDINFO("the depot [%s] has no web app repository code", 1403 depotName.String()); 1404 } else 1405 return true; 1406 } 1407 } 1408 1409 return false; 1410 } 1411 1412 1413 void 1414 MainWindow::_RatePackage() 1415 { 1416 if (!_SelectedPackageHasWebAppRepositoryCode()) { 1417 BAlert* alert = new(std::nothrow) BAlert( 1418 B_TRANSLATE("Rating not possible"), 1419 B_TRANSLATE("This package doesn't seem to be on the HaikuDepot " 1420 "Server, so it's not possible to create a new rating " 1421 "or edit an existing rating."), 1422 B_TRANSLATE("OK")); 1423 alert->Go(); 1424 return; 1425 } 1426 1427 if (fModel.Nickname().IsEmpty()) { 1428 BAlert* alert = new(std::nothrow) BAlert( 1429 B_TRANSLATE("Not logged in"), 1430 B_TRANSLATE("You need to be logged into an account before you " 1431 "can rate packages."), 1432 B_TRANSLATE("Cancel"), 1433 B_TRANSLATE("Login or Create account")); 1434 1435 if (alert == NULL) 1436 return; 1437 1438 int32 choice = alert->Go(); 1439 if (choice == 1) 1440 _OpenLoginWindow(BMessage(MSG_RATE_PACKAGE)); 1441 return; 1442 } 1443 1444 // TODO: Allow only one RatePackageWindow 1445 // TODO: Mechanism for remembering the window frame 1446 RatePackageWindow* window = new RatePackageWindow(this, 1447 BRect(0, 0, 500, 400), fModel); 1448 window->SetPackage(fPackageInfoView->Package()); 1449 window->Show(); 1450 } 1451 1452 1453 void 1454 MainWindow::_ShowScreenshot() 1455 { 1456 // TODO: Mechanism for remembering the window frame 1457 if (fScreenshotWindow == NULL) 1458 fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400), &fModel); 1459 1460 if (fScreenshotWindow->LockWithTimeout(1000) != B_OK) 1461 return; 1462 1463 fScreenshotWindow->SetPackage(fPackageInfoView->Package()); 1464 1465 if (fScreenshotWindow->IsHidden()) 1466 fScreenshotWindow->Show(); 1467 else 1468 fScreenshotWindow->Activate(); 1469 1470 fScreenshotWindow->Unlock(); 1471 } 1472 1473 1474 void 1475 MainWindow::_ViewUserUsageConditions( 1476 UserUsageConditionsSelectionMode mode) 1477 { 1478 UserUsageConditionsWindow* window = new UserUsageConditionsWindow( 1479 fModel, mode); 1480 window->Show(); 1481 } 1482 1483 1484 void 1485 MainWindow::UserCredentialsFailed() 1486 { 1487 BString message = B_TRANSLATE("The password previously " 1488 "supplied for the user [%Nickname%] is not currently " 1489 "valid. The user will be logged-out of this application " 1490 "and you should login again with your updated password."); 1491 message.ReplaceAll("%Nickname%", fModel.Nickname()); 1492 1493 AppUtils::NotifySimpleError(B_TRANSLATE("Login issue"), 1494 message); 1495 1496 { 1497 AutoLocker<BLocker> locker(fModel.Lock()); 1498 fModel.SetNickname(""); 1499 } 1500 } 1501 1502 1503 /*! \brief This method is invoked from the UserDetailVerifierProcess on a 1504 background thread. For this reason it lodges a message into itself 1505 which can then be handled on the main thread. 1506 */ 1507 1508 void 1509 MainWindow::UserUsageConditionsNotLatest(const UserDetail& userDetail) 1510 { 1511 BMessage message(MSG_USER_USAGE_CONDITIONS_NOT_LATEST); 1512 BMessage detailsMessage; 1513 if (userDetail.Archive(&detailsMessage, true) != B_OK 1514 || message.AddMessage("userDetail", &detailsMessage) != B_OK) { 1515 HDERROR("unable to archive the user detail into a message"); 1516 } 1517 else 1518 BMessenger(this).SendMessage(&message); 1519 } 1520 1521 1522 void 1523 MainWindow::_HandleUserUsageConditionsNotLatest( 1524 const UserDetail& userDetail) 1525 { 1526 ToLatestUserUsageConditionsWindow* window = 1527 new ToLatestUserUsageConditionsWindow(this, fModel, userDetail); 1528 window->Show(); 1529 } 1530 1531 1532 void 1533 MainWindow::_AddProcessCoordinator(ProcessCoordinator* item) 1534 { 1535 AutoLocker<BLocker> lock(&fCoordinatorLock); 1536 1537 if (fShouldCloseWhenNoProcessesToCoordinate) { 1538 HDINFO("system shutting down --> new process coordinator [%s] rejected", 1539 item->Name().String()); 1540 return; 1541 } 1542 1543 item->SetListener(this); 1544 1545 if (fCoordinator == NULL) { 1546 if (acquire_sem(fCoordinatorRunningSem) != B_OK) 1547 debugger("unable to acquire the process coordinator sem"); 1548 HDINFO("adding and starting a process coordinator [%s]", 1549 item->Name().String()); 1550 delete fCoordinator; 1551 fCoordinator = item; 1552 fCoordinator->Start(); 1553 } else { 1554 HDINFO("adding process coordinator [%s] to the queue", 1555 item->Name().String()); 1556 fCoordinatorQueue.push(item); 1557 } 1558 } 1559 1560 1561 void 1562 MainWindow::_SpinUntilProcessCoordinatorComplete() 1563 { 1564 while (true) { 1565 if (acquire_sem(fCoordinatorRunningSem) != B_OK) 1566 debugger("unable to acquire the process coordinator sem"); 1567 if (release_sem(fCoordinatorRunningSem) != B_OK) 1568 debugger("unable to release the process coordinator sem"); 1569 { 1570 AutoLocker<BLocker> lock(&fCoordinatorLock); 1571 if (fCoordinator == NULL) 1572 return; 1573 } 1574 } 1575 } 1576 1577 1578 void 1579 MainWindow::_StopProcessCoordinators() 1580 { 1581 HDINFO("will stop all queued process coordinators"); 1582 AutoLocker<BLocker> lock(&fCoordinatorLock); 1583 1584 while (!fCoordinatorQueue.empty()) { 1585 ProcessCoordinator* processCoordinator 1586 = fCoordinatorQueue.front(); 1587 HDINFO("will drop queued process coordinator [%s]", 1588 processCoordinator->Name().String()); 1589 fCoordinatorQueue.pop(); 1590 delete processCoordinator; 1591 } 1592 1593 if (fCoordinator != NULL) 1594 fCoordinator->RequestStop(); 1595 } 1596 1597 1598 /*! This method is called when there is some change in the bulk load process 1599 or other process coordinator. 1600 A change may mean that a new process has started / stopped etc... or it 1601 may mean that the entire coordinator has finished. 1602 */ 1603 1604 void 1605 MainWindow::CoordinatorChanged(ProcessCoordinatorState& coordinatorState) 1606 { 1607 BMessage message(MSG_PROCESS_COORDINATOR_CHANGED); 1608 if (coordinatorState.Archive(&message, true) != B_OK) { 1609 HDFATAL("unable to archive message when the process coordinator" 1610 " has changed"); 1611 } 1612 BMessenger(this).SendMessage(&message); 1613 } 1614 1615 1616 void 1617 MainWindow::_HandleProcessCoordinatorChanged(ProcessCoordinatorState& coordinatorState) 1618 { 1619 AutoLocker<BLocker> lock(&fCoordinatorLock); 1620 1621 if (fCoordinator->Identifier() 1622 == coordinatorState.ProcessCoordinatorIdentifier()) { 1623 if (!coordinatorState.IsRunning()) { 1624 if (release_sem(fCoordinatorRunningSem) != B_OK) 1625 debugger("unable to release the process coordinator sem"); 1626 HDINFO("process coordinator [%s] did complete", 1627 fCoordinator->Name().String()); 1628 // complete the last one that just finished 1629 BMessage* message = fCoordinator->Message(); 1630 1631 if (message != NULL) { 1632 BMessenger messenger(this); 1633 message->AddInt64(KEY_ERROR_STATUS, 1634 (int64) fCoordinator->ErrorStatus()); 1635 messenger.SendMessage(message); 1636 } 1637 1638 HDDEBUG("process coordinator report;\n---\n%s\n----", 1639 fCoordinator->LogReport().String()); 1640 1641 delete fCoordinator; 1642 fCoordinator = NULL; 1643 1644 // now schedule the next one. 1645 if (!fCoordinatorQueue.empty()) { 1646 if (acquire_sem(fCoordinatorRunningSem) != B_OK) 1647 debugger("unable to acquire the process coordinator sem"); 1648 fCoordinator = fCoordinatorQueue.front(); 1649 HDINFO("starting next process coordinator [%s]", 1650 fCoordinator->Name().String()); 1651 fCoordinatorQueue.pop(); 1652 fCoordinator->Start(); 1653 } 1654 else { 1655 _NotifyWorkStatusClear(); 1656 if (fShouldCloseWhenNoProcessesToCoordinate) { 1657 HDINFO("no more processes to coord --> will quit"); 1658 BMessage message(B_QUIT_REQUESTED); 1659 PostMessage(&message); 1660 } 1661 } 1662 } 1663 else { 1664 _NotifyWorkStatusChange(coordinatorState.Message(), 1665 coordinatorState.Progress()); 1666 // show the progress to the user. 1667 } 1668 } else { 1669 _NotifyWorkStatusClear(); 1670 HDINFO("! unknown process coordinator changed"); 1671 } 1672 } 1673 1674 1675 static package_list_view_mode 1676 main_window_tab_to_package_list_view_mode(int32 tab) 1677 { 1678 if (tab == TAB_PROMINENT_PACKAGES) 1679 return PROMINENT; 1680 return ALL; 1681 } 1682 1683 1684 void 1685 MainWindow::_HandleChangePackageListViewMode() 1686 { 1687 package_list_view_mode tabMode = main_window_tab_to_package_list_view_mode( 1688 fListTabs->Selection()); 1689 package_list_view_mode modelMode = fModel.PackageListViewMode(); 1690 1691 if (tabMode != modelMode) { 1692 BAutolock locker(fModel.Lock()); 1693 fModel.SetPackageListViewMode(tabMode); 1694 } 1695 } 1696 1697 1698 std::vector<DepotInfoRef> 1699 MainWindow::_CreateSnapshotOfDepots() 1700 { 1701 std::vector<DepotInfoRef> result; 1702 BAutolock locker(fModel.Lock()); 1703 int32 countDepots = fModel.CountDepots(); 1704 for(int32 i = 0; i < countDepots; i++) 1705 result.push_back(fModel.DepotAtIndex(i)); 1706 return result; 1707 } 1708 1709 1710 /*! This will get invoked in the case that a screenshot has been cached 1711 and so could now be loaded by some UI element. This method will then 1712 signal to other UI elements that they could load a screenshot should 1713 they have been waiting for it. 1714 */ 1715 1716 void 1717 MainWindow::_HandleScreenshotCached(const BMessage* message) 1718 { 1719 ScreenshotCoordinate coordinate(message); 1720 fPackageInfoView->HandleScreenshotCached(coordinate); 1721 } 1722