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