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