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, Andrew Lindesay <apl@lindesay.co.nz>. 7 * Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>. 8 * All rights reserved. Distributed under the terms of the MIT License. 9 */ 10 11 12 #include "MainWindow.h" 13 14 #include <map> 15 16 #include <stdio.h> 17 18 #include <Alert.h> 19 #include <Autolock.h> 20 #include <Application.h> 21 #include <Button.h> 22 #include <Catalog.h> 23 #include <CardLayout.h> 24 #include <LayoutBuilder.h> 25 #include <MenuBar.h> 26 #include <MenuItem.h> 27 #include <Messenger.h> 28 #include <Roster.h> 29 #include <Screen.h> 30 #include <ScrollView.h> 31 #include <StringList.h> 32 #include <StringView.h> 33 #include <TabView.h> 34 35 #include <package/Context.h> 36 #include <package/manager/Exceptions.h> 37 #include <package/manager/RepositoryBuilder.h> 38 #include <package/RefreshRepositoryRequest.h> 39 #include <package/PackageRoster.h> 40 #include "package/RepositoryCache.h" 41 #include <package/solver/SolverPackage.h> 42 #include <package/solver/SolverProblem.h> 43 #include <package/solver/SolverProblemSolution.h> 44 #include <package/solver/SolverRepository.h> 45 #include <package/solver/SolverResult.h> 46 47 #include "AutoDeleter.h" 48 #include "AutoLocker.h" 49 #include "DecisionProvider.h" 50 #include "FeaturedPackagesView.h" 51 #include "FilterView.h" 52 #include "JobStateListener.h" 53 #include "PackageInfoView.h" 54 #include "PackageListView.h" 55 #include "PackageManager.h" 56 #include "RatePackageWindow.h" 57 #include "support.h" 58 #include "ScreenshotWindow.h" 59 #include "UserLoginWindow.h" 60 #include "WorkStatusView.h" 61 62 63 #undef B_TRANSLATION_CONTEXT 64 #define B_TRANSLATION_CONTEXT "MainWindow" 65 66 67 enum { 68 MSG_MODEL_WORKER_DONE = 'mmwd', 69 MSG_REFRESH_REPOS = 'mrrp', 70 MSG_MANAGE_REPOS = 'mmrp', 71 MSG_SOFTWARE_UPDATER = 'mswu', 72 MSG_LOG_IN = 'lgin', 73 MSG_LOG_OUT = 'lgot', 74 MSG_AUTHORIZATION_CHANGED = 'athc', 75 MSG_PACKAGE_CHANGED = 'pchd', 76 77 MSG_SHOW_FEATURED_PACKAGES = 'sofp', 78 MSG_SHOW_AVAILABLE_PACKAGES = 'savl', 79 MSG_SHOW_INSTALLED_PACKAGES = 'sins', 80 MSG_SHOW_SOURCE_PACKAGES = 'ssrc', 81 MSG_SHOW_DEVELOP_PACKAGES = 'sdvl' 82 }; 83 84 85 using namespace BPackageKit; 86 using namespace BPackageKit::BManager::BPrivate; 87 88 89 typedef std::map<BString, PackageInfoRef> PackageInfoMap; 90 typedef std::map<BString, DepotInfo> DepotInfoMap; 91 92 93 struct RefreshWorkerParameters { 94 MainWindow* window; 95 bool forceRefresh; 96 97 RefreshWorkerParameters(MainWindow* window, bool forceRefresh) 98 : 99 window(window), 100 forceRefresh(forceRefresh) 101 { 102 } 103 }; 104 105 106 class MessageModelListener : public ModelListener { 107 public: 108 MessageModelListener(const BMessenger& messenger) 109 : 110 fMessenger(messenger) 111 { 112 } 113 114 virtual void AuthorizationChanged() 115 { 116 if (fMessenger.IsValid()) 117 fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED); 118 } 119 120 private: 121 BMessenger fMessenger; 122 }; 123 124 125 MainWindow::MainWindow(const BMessage& settings) 126 : 127 BWindow(BRect(50, 50, 650, 550), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"), 128 B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 129 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 130 fScreenshotWindow(NULL), 131 fUserMenu(NULL), 132 fLogInItem(NULL), 133 fLogOutItem(NULL), 134 fModelListener(new MessageModelListener(BMessenger(this)), true), 135 fBulkLoadStateMachine(&fModel), 136 fTerminating(false), 137 fSinglePackageMode(false), 138 fModelWorker(B_BAD_THREAD_ID) 139 { 140 BMenuBar* menuBar = new BMenuBar(B_TRANSLATE("Main Menu")); 141 _BuildMenu(menuBar); 142 143 BMenuBar* userMenuBar = new BMenuBar(B_TRANSLATE("User Menu")); 144 _BuildUserMenu(userMenuBar); 145 set_small_font(userMenuBar); 146 userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET, 147 menuBar->MaxSize().height)); 148 149 fFilterView = new FilterView(); 150 fFeaturedPackagesView = new FeaturedPackagesView(); 151 fPackageListView = new PackageListView(fModel.Lock()); 152 fPackageInfoView = new PackageInfoView(fModel.Lock(), this); 153 154 fSplitView = new BSplitView(B_VERTICAL, 5.0f); 155 156 BGroupView* featuredPackagesGroup = new BGroupView(B_VERTICAL); 157 BStringView* featuredPackagesTitle = new BStringView( 158 "featured packages title", B_TRANSLATE("Featured packages")); 159 BFont font(be_bold_font); 160 font.SetSize(font.Size() * 1.3f); 161 featuredPackagesTitle->SetFont(&font); 162 featuredPackagesGroup->SetExplicitMaxSize( 163 BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 164 BLayoutBuilder::Group<>(featuredPackagesGroup) 165 .Add(featuredPackagesTitle) 166 .Add(fFeaturedPackagesView) 167 ; 168 169 fWorkStatusView = new WorkStatusView("work status"); 170 fPackageListView->AttachWorkStatusView(fWorkStatusView); 171 172 BView* listArea = new BView("list area", 0); 173 fListLayout = new BCardLayout(); 174 listArea->SetLayout(fListLayout); 175 listArea->AddChild(featuredPackagesGroup); 176 listArea->AddChild(fPackageListView); 177 178 BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f) 179 .AddGroup(B_HORIZONTAL, 0.0f) 180 .Add(menuBar, 1.0f) 181 .Add(userMenuBar, 0.0f) 182 .End() 183 .Add(fFilterView) 184 .AddSplit(fSplitView) 185 .AddGroup(B_VERTICAL) 186 .Add(listArea) 187 .SetInsets( 188 B_USE_DEFAULT_SPACING, 0.0f, 189 B_USE_DEFAULT_SPACING, 0.0f) 190 .End() 191 .Add(fPackageInfoView) 192 .End() 193 .Add(fWorkStatusView) 194 ; 195 196 fSplitView->SetCollapsible(0, false); 197 fSplitView->SetCollapsible(1, false); 198 199 fModel.AddListener(fModelListener); 200 201 // Restore settings 202 BMessage columnSettings; 203 if (settings.FindMessage("column settings", &columnSettings) == B_OK) 204 fPackageListView->LoadState(&columnSettings); 205 206 bool showOption; 207 if (settings.FindBool("show featured packages", &showOption) == B_OK) 208 fModel.SetShowFeaturedPackages(showOption); 209 if (settings.FindBool("show available packages", &showOption) == B_OK) 210 fModel.SetShowAvailablePackages(showOption); 211 if (settings.FindBool("show installed packages", &showOption) == B_OK) 212 fModel.SetShowInstalledPackages(showOption); 213 if (settings.FindBool("show develop packages", &showOption) == B_OK) 214 fModel.SetShowDevelopPackages(showOption); 215 if (settings.FindBool("show source packages", &showOption) == B_OK) 216 fModel.SetShowSourcePackages(showOption); 217 218 if (fModel.ShowFeaturedPackages()) 219 fListLayout->SetVisibleItem((int32)0); 220 else 221 fListLayout->SetVisibleItem(1); 222 223 _RestoreUserName(settings); 224 _RestoreWindowFrame(settings); 225 226 atomic_set(&fPackagesToShowListID, 0); 227 228 // start worker threads 229 BPackageRoster().StartWatching(this, 230 B_WATCH_PACKAGE_INSTALLATION_LOCATIONS); 231 232 _StartRefreshWorker(); 233 234 _InitWorkerThreads(); 235 } 236 237 238 MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package) 239 : 240 BWindow(BRect(50, 50, 650, 350), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"), 241 B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 242 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 243 fWorkStatusView(NULL), 244 fScreenshotWindow(NULL), 245 fUserMenu(NULL), 246 fLogInItem(NULL), 247 fLogOutItem(NULL), 248 fModelListener(new MessageModelListener(BMessenger(this)), true), 249 fBulkLoadStateMachine(&fModel), 250 fTerminating(false), 251 fSinglePackageMode(true), 252 fModelWorker(B_BAD_THREAD_ID) 253 { 254 fFilterView = new FilterView(); 255 fPackageListView = new PackageListView(fModel.Lock()); 256 fPackageInfoView = new PackageInfoView(fModel.Lock(), this); 257 258 BLayoutBuilder::Group<>(this, B_VERTICAL) 259 .Add(fPackageInfoView) 260 .SetInsets(0, B_USE_WINDOW_INSETS, 0, 0) 261 ; 262 263 fModel.AddListener(fModelListener); 264 265 // Restore settings 266 _RestoreUserName(settings); 267 _RestoreWindowFrame(settings); 268 269 fPackageInfoView->SetPackage(package); 270 271 _InitWorkerThreads(); 272 } 273 274 275 MainWindow::~MainWindow() 276 { 277 BPackageRoster().StopWatching(this); 278 279 fTerminating = true; 280 if (fModelWorker >= 0) 281 wait_for_thread(fModelWorker, NULL); 282 283 delete_sem(fPendingActionsSem); 284 if (fPendingActionsWorker >= 0) 285 wait_for_thread(fPendingActionsWorker, NULL); 286 287 delete_sem(fPackageToPopulateSem); 288 if (fPopulatePackageWorker >= 0) 289 wait_for_thread(fPopulatePackageWorker, NULL); 290 291 delete_sem(fNewPackagesToShowSem); 292 delete_sem(fShowPackagesAcknowledgeSem); 293 if (fShowPackagesWorker >= 0) 294 wait_for_thread(fShowPackagesWorker, NULL); 295 296 if (fScreenshotWindow != NULL && fScreenshotWindow->Lock()) 297 fScreenshotWindow->Quit(); 298 } 299 300 301 bool 302 MainWindow::QuitRequested() 303 { 304 BMessage settings; 305 StoreSettings(settings); 306 307 BMessage message(MSG_MAIN_WINDOW_CLOSED); 308 message.AddMessage("window settings", &settings); 309 310 be_app->PostMessage(&message); 311 312 return true; 313 } 314 315 316 void 317 MainWindow::MessageReceived(BMessage* message) 318 { 319 switch (message->what) { 320 case MSG_MODEL_WORKER_DONE: 321 { 322 fModelWorker = B_BAD_THREAD_ID; 323 _AdoptModel(); 324 fFilterView->AdoptModel(fModel); 325 fWorkStatusView->SetIdle(); 326 break; 327 } 328 case B_SIMPLE_DATA: 329 case B_REFS_RECEIVED: 330 // TODO: ? 331 break; 332 333 case B_PACKAGE_UPDATE: 334 // TODO: We should do a more selective update depending on the 335 // "event", "location", and "change count" fields! 336 _StartRefreshWorker(false); 337 break; 338 339 case MSG_REFRESH_REPOS: 340 _StartRefreshWorker(true); 341 break; 342 343 case MSG_MANAGE_REPOS: 344 be_roster->Launch("application/x-vnd.Haiku-Repositories"); 345 break; 346 347 case MSG_SOFTWARE_UPDATER: 348 be_roster->Launch("application/x-vnd.haiku-softwareupdater"); 349 break; 350 351 case MSG_LOG_IN: 352 _OpenLoginWindow(BMessage()); 353 break; 354 355 case MSG_LOG_OUT: 356 fModel.SetUsername(""); 357 break; 358 359 case MSG_AUTHORIZATION_CHANGED: 360 _UpdateAuthorization(); 361 break; 362 363 case MSG_SHOW_FEATURED_PACKAGES: 364 { 365 BAutolock locker(fModel.Lock()); 366 fModel.SetShowFeaturedPackages( 367 !fModel.ShowFeaturedPackages()); 368 } 369 _AdoptModel(); 370 break; 371 372 case MSG_SHOW_AVAILABLE_PACKAGES: 373 { 374 BAutolock locker(fModel.Lock()); 375 fModel.SetShowAvailablePackages( 376 !fModel.ShowAvailablePackages()); 377 } 378 _AdoptModel(); 379 break; 380 381 case MSG_SHOW_INSTALLED_PACKAGES: 382 { 383 BAutolock locker(fModel.Lock()); 384 fModel.SetShowInstalledPackages( 385 !fModel.ShowInstalledPackages()); 386 } 387 _AdoptModel(); 388 break; 389 390 case MSG_SHOW_SOURCE_PACKAGES: 391 { 392 BAutolock locker(fModel.Lock()); 393 fModel.SetShowSourcePackages(!fModel.ShowSourcePackages()); 394 } 395 _AdoptModel(); 396 break; 397 398 case MSG_SHOW_DEVELOP_PACKAGES: 399 { 400 BAutolock locker(fModel.Lock()); 401 fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages()); 402 } 403 _AdoptModel(); 404 break; 405 406 case MSG_PACKAGE_SELECTED: 407 { 408 BString name; 409 if (message->FindString("name", &name) == B_OK) { 410 BAutolock locker(fModel.Lock()); 411 int count = fVisiblePackages.CountItems(); 412 for (int i = 0; i < count; i++) { 413 const PackageInfoRef& package 414 = fVisiblePackages.ItemAtFast(i); 415 if (package.Get() != NULL && package->Name() == name) { 416 locker.Unlock(); 417 _AdoptPackage(package); 418 break; 419 } 420 } 421 } else { 422 _ClearPackage(); 423 } 424 break; 425 } 426 427 case MSG_CATEGORY_SELECTED: 428 { 429 BString name; 430 if (message->FindString("name", &name) != B_OK) 431 name = ""; 432 { 433 BAutolock locker(fModel.Lock()); 434 fModel.SetCategory(name); 435 } 436 _AdoptModel(); 437 break; 438 } 439 440 case MSG_DEPOT_SELECTED: 441 { 442 BString name; 443 if (message->FindString("name", &name) != B_OK) 444 name = ""; 445 { 446 BAutolock locker(fModel.Lock()); 447 fModel.SetDepot(name); 448 } 449 _AdoptModel(); 450 break; 451 } 452 453 case MSG_SEARCH_TERMS_MODIFIED: 454 { 455 // TODO: Do this with a delay! 456 BString searchTerms; 457 if (message->FindString("search terms", &searchTerms) != B_OK) 458 searchTerms = ""; 459 { 460 BAutolock locker(fModel.Lock()); 461 fModel.SetSearchTerms(searchTerms); 462 } 463 _AdoptModel(); 464 break; 465 } 466 467 case MSG_PACKAGE_CHANGED: 468 { 469 PackageInfo* info; 470 if (message->FindPointer("package", (void**)&info) == B_OK) { 471 PackageInfoRef ref(info, true); 472 uint32 changes; 473 if (message->FindUInt32("changes", &changes) != B_OK) 474 changes = 0; 475 if ((changes & PKG_CHANGED_STATE) != 0) { 476 BAutolock locker(fModel.Lock()); 477 fModel.SetPackageState(ref, ref->State()); 478 } 479 480 // Asynchronous updates to the package information 481 // can mean that the package needs to be added or 482 // removed to/from the visible packages when the current 483 // filter parameters are re-evaluated on this package. 484 bool wasVisible = fVisiblePackages.Contains(ref); 485 bool isVisible; 486 { 487 BAutolock locker(fModel.Lock()); 488 // The package didn't get a chance yet to be in the 489 // visible package list 490 isVisible = fModel.MatchesFilter(ref); 491 492 // Transfer this single package, otherwise we miss 493 // other packages if they appear or disappear along 494 // with this one before receive a notification for 495 // them. 496 if (isVisible) { 497 fVisiblePackages.Add(ref); 498 } else if (wasVisible) 499 fVisiblePackages.Remove(ref); 500 } 501 502 if (wasVisible != isVisible) { 503 if (!isVisible) { 504 fPackageListView->RemovePackage(ref); 505 fFeaturedPackagesView->RemovePackage(ref); 506 } else { 507 fPackageListView->AddPackage(ref); 508 if (ref->IsProminent()) 509 fFeaturedPackagesView->AddPackage(ref); 510 } 511 } 512 513 if (!fSinglePackageMode && (changes & PKG_CHANGED_STATE) != 0) 514 fWorkStatusView->PackageStatusChanged(ref); 515 } 516 break; 517 } 518 519 case MSG_RATE_PACKAGE: 520 _RatePackage(); 521 break; 522 523 case MSG_SHOW_SCREENSHOT: 524 _ShowScreenshot(); 525 break; 526 527 case MSG_PACKAGE_WORKER_BUSY: 528 { 529 BString reason; 530 status_t status = message->FindString("reason", &reason); 531 if (status != B_OK) 532 break; 533 if (!fSinglePackageMode) 534 fWorkStatusView->SetBusy(reason); 535 break; 536 } 537 538 case MSG_PACKAGE_WORKER_IDLE: 539 if (!fSinglePackageMode) 540 fWorkStatusView->SetIdle(); 541 break; 542 543 case MSG_ADD_VISIBLE_PACKAGES: 544 { 545 struct SemaphoreReleaser { 546 SemaphoreReleaser(sem_id semaphore) 547 : 548 fSemaphore(semaphore) 549 { } 550 551 ~SemaphoreReleaser() { release_sem(fSemaphore); } 552 553 sem_id fSemaphore; 554 }; 555 556 // Make sure acknowledge semaphore is released even on error, 557 // so the worker thread won't be blocked 558 SemaphoreReleaser acknowledger(fShowPackagesAcknowledgeSem); 559 560 int32 numPackages = 0; 561 type_code unused; 562 status_t status = message->GetInfo("package_ref", &unused, 563 &numPackages); 564 if (status != B_OK || numPackages == 0) 565 break; 566 567 int32 listID = 0; 568 status = message->FindInt32("list_id", &listID); 569 if (status != B_OK) 570 break; 571 if (listID != atomic_get(&fPackagesToShowListID)) { 572 // list is outdated, ignore 573 break; 574 } 575 576 for (int i = 0; i < numPackages; i++) { 577 PackageInfo* packageRaw = NULL; 578 status = message->FindPointer("package_ref", i, 579 (void**)&packageRaw); 580 if (status != B_OK) 581 break; 582 PackageInfoRef package(packageRaw, true); 583 584 fPackageListView->AddPackage(package); 585 if (package->IsProminent()) 586 fFeaturedPackagesView->AddPackage(package); 587 } 588 break; 589 } 590 591 case MSG_UPDATE_SELECTED_PACKAGE: 592 { 593 const PackageInfoRef& selectedPackage = fPackageInfoView->Package(); 594 fFeaturedPackagesView->SelectPackage(selectedPackage, true); 595 fPackageListView->SelectPackage(selectedPackage); 596 597 AutoLocker<BLocker> modelLocker(fModel.Lock()); 598 if (!fVisiblePackages.Contains(fPackageInfoView->Package())) 599 fPackageInfoView->Clear(); 600 break; 601 } 602 603 default: 604 BWindow::MessageReceived(message); 605 break; 606 } 607 } 608 609 610 void 611 MainWindow::StoreSettings(BMessage& settings) const 612 { 613 settings.AddRect(_WindowFrameName(), Frame()); 614 if (!fSinglePackageMode) { 615 settings.AddRect("window frame", Frame()); 616 617 BMessage columnSettings; 618 fPackageListView->SaveState(&columnSettings); 619 620 settings.AddMessage("column settings", &columnSettings); 621 622 settings.AddBool("show featured packages", 623 fModel.ShowFeaturedPackages()); 624 settings.AddBool("show available packages", 625 fModel.ShowAvailablePackages()); 626 settings.AddBool("show installed packages", 627 fModel.ShowInstalledPackages()); 628 settings.AddBool("show develop packages", fModel.ShowDevelopPackages()); 629 settings.AddBool("show source packages", fModel.ShowSourcePackages()); 630 } 631 632 settings.AddString("username", fModel.Username()); 633 } 634 635 636 void 637 MainWindow::PackageChanged(const PackageInfoEvent& event) 638 { 639 uint32 whatchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE; 640 if ((event.Changes() & whatchedChanges) != 0) { 641 PackageInfoRef ref(event.Package()); 642 BMessage message(MSG_PACKAGE_CHANGED); 643 message.AddPointer("package", ref.Get()); 644 message.AddUInt32("changes", event.Changes()); 645 ref.Detach(); 646 // reference needs to be released by MessageReceived(); 647 PostMessage(&message); 648 } 649 } 650 651 652 status_t 653 MainWindow::SchedulePackageActions(PackageActionList& list) 654 { 655 AutoLocker<BLocker> lock(&fPendingActionsLock); 656 for (int32 i = 0; i < list.CountItems(); i++) { 657 if (!fPendingActions.Add(list.ItemAtFast(i))) 658 return B_NO_MEMORY; 659 } 660 661 return release_sem_etc(fPendingActionsSem, list.CountItems(), 0); 662 } 663 664 665 Model* 666 MainWindow::GetModel() 667 { 668 return &fModel; 669 } 670 671 672 void 673 MainWindow::_BuildMenu(BMenuBar* menuBar) 674 { 675 BMenu* menu = new BMenu(B_TRANSLATE("Tools")); 676 menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh repositories"), 677 new BMessage(MSG_REFRESH_REPOS))); 678 menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories" 679 B_UTF8_ELLIPSIS), new BMessage(MSG_MANAGE_REPOS))); 680 menu->AddItem(new BMenuItem(B_TRANSLATE("Check for updates" 681 B_UTF8_ELLIPSIS), new BMessage(MSG_SOFTWARE_UPDATER))); 682 683 menuBar->AddItem(menu); 684 685 menu = new BMenu(B_TRANSLATE("Show")); 686 687 fShowFeaturedPackagesItem = new BMenuItem( 688 B_TRANSLATE("Only featured packages"), 689 new BMessage(MSG_SHOW_FEATURED_PACKAGES)); 690 menu->AddItem(fShowFeaturedPackagesItem); 691 692 menu->AddSeparatorItem(); 693 694 fShowAvailablePackagesItem = new BMenuItem( 695 B_TRANSLATE("Available packages"), 696 new BMessage(MSG_SHOW_AVAILABLE_PACKAGES)); 697 menu->AddItem(fShowAvailablePackagesItem); 698 699 fShowInstalledPackagesItem = new BMenuItem( 700 B_TRANSLATE("Installed packages"), 701 new BMessage(MSG_SHOW_INSTALLED_PACKAGES)); 702 menu->AddItem(fShowInstalledPackagesItem); 703 704 menu->AddSeparatorItem(); 705 706 fShowDevelopPackagesItem = new BMenuItem( 707 B_TRANSLATE("Develop packages"), 708 new BMessage(MSG_SHOW_DEVELOP_PACKAGES)); 709 menu->AddItem(fShowDevelopPackagesItem); 710 711 fShowSourcePackagesItem = new BMenuItem( 712 B_TRANSLATE("Source packages"), 713 new BMessage(MSG_SHOW_SOURCE_PACKAGES)); 714 menu->AddItem(fShowSourcePackagesItem); 715 716 menuBar->AddItem(menu); 717 } 718 719 720 void 721 MainWindow::_BuildUserMenu(BMenuBar* menuBar) 722 { 723 fUserMenu = new BMenu(B_TRANSLATE("Not logged in")); 724 725 fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS), 726 new BMessage(MSG_LOG_IN)); 727 fUserMenu->AddItem(fLogInItem); 728 729 fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"), 730 new BMessage(MSG_LOG_OUT)); 731 fUserMenu->AddItem(fLogOutItem); 732 733 menuBar->AddItem(fUserMenu); 734 } 735 736 737 void 738 MainWindow::_RestoreUserName(const BMessage& settings) 739 { 740 BString username; 741 if (settings.FindString("username", &username) == B_OK 742 && username.Length() > 0) { 743 fModel.SetUsername(username); 744 } 745 } 746 747 748 const char* 749 MainWindow::_WindowFrameName() const 750 { 751 if (fSinglePackageMode) 752 return "small window frame"; 753 754 return "window frame"; 755 } 756 757 758 void 759 MainWindow::_RestoreWindowFrame(const BMessage& settings) 760 { 761 BRect frame = Frame(); 762 763 BRect windowFrame; 764 bool fromSettings = false; 765 if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) { 766 frame = windowFrame; 767 fromSettings = true; 768 } else if (!fSinglePackageMode) { 769 // Resize to occupy a certain screen size 770 BRect screenFrame = BScreen(this).Frame(); 771 float width = frame.Width(); 772 float height = frame.Height(); 773 if (width < screenFrame.Width() * .666f 774 && height < screenFrame.Height() * .666f) { 775 frame.bottom = frame.top + screenFrame.Height() * .666f; 776 frame.right = frame.left 777 + std::min(screenFrame.Width() * .666f, height * 7 / 5); 778 } 779 } 780 781 MoveTo(frame.LeftTop()); 782 ResizeTo(frame.Width(), frame.Height()); 783 784 if (fromSettings) 785 MoveOnScreen(); 786 else 787 CenterOnScreen(); 788 } 789 790 791 void 792 MainWindow::_InitWorkerThreads() 793 { 794 fPendingActionsSem = create_sem(0, "PendingPackageActions"); 795 if (fPendingActionsSem >= 0) { 796 fPendingActionsWorker = spawn_thread(&_PackageActionWorker, 797 "Planet Express", B_NORMAL_PRIORITY, this); 798 if (fPendingActionsWorker >= 0) 799 resume_thread(fPendingActionsWorker); 800 } else 801 fPendingActionsWorker = -1; 802 803 fPackageToPopulateSem = create_sem(0, "PopulatePackage"); 804 if (fPackageToPopulateSem >= 0) { 805 fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker, 806 "Package Populator", B_NORMAL_PRIORITY, this); 807 if (fPopulatePackageWorker >= 0) 808 resume_thread(fPopulatePackageWorker); 809 } else 810 fPopulatePackageWorker = -1; 811 812 fNewPackagesToShowSem = create_sem(0, "ShowPackages"); 813 fShowPackagesAcknowledgeSem = create_sem(0, "ShowPackagesAck"); 814 if (fNewPackagesToShowSem >= 0 && fShowPackagesAcknowledgeSem >= 0) { 815 fShowPackagesWorker = spawn_thread(&_PackagesToShowWorker, 816 "Good news everyone", B_NORMAL_PRIORITY, this); 817 if (fShowPackagesWorker >= 0) 818 resume_thread(fShowPackagesWorker); 819 } else 820 fShowPackagesWorker = -1; 821 } 822 823 824 void 825 MainWindow::_AdoptModel() 826 { 827 fVisiblePackages = fModel.CreatePackageList(); 828 829 { 830 AutoLocker<BLocker> modelLocker(fModel.Lock()); 831 AutoLocker<BLocker> listLocker(fPackagesToShowListLock); 832 fPackagesToShowList = fVisiblePackages; 833 atomic_add(&fPackagesToShowListID, 1); 834 } 835 836 fFeaturedPackagesView->Clear(); 837 fPackageListView->Clear(); 838 839 release_sem(fNewPackagesToShowSem); 840 841 BAutolock locker(fModel.Lock()); 842 fShowFeaturedPackagesItem->SetMarked(fModel.ShowFeaturedPackages()); 843 fShowFeaturedPackagesItem->SetEnabled(fModel.SearchTerms() == ""); 844 fShowAvailablePackagesItem->SetMarked(fModel.ShowAvailablePackages()); 845 fShowInstalledPackagesItem->SetMarked(fModel.ShowInstalledPackages()); 846 fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages()); 847 fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages()); 848 849 if (fModel.ShowFeaturedPackages() && fModel.SearchTerms() == "") 850 fListLayout->SetVisibleItem((int32)0); 851 else 852 fListLayout->SetVisibleItem((int32)1); 853 } 854 855 856 void 857 MainWindow::_AdoptPackage(const PackageInfoRef& package) 858 { 859 { 860 BAutolock locker(fModel.Lock()); 861 fPackageInfoView->SetPackage(package); 862 863 if (fFeaturedPackagesView != NULL) 864 fFeaturedPackagesView->SelectPackage(package); 865 if (fPackageListView != NULL) 866 fPackageListView->SelectPackage(package); 867 } 868 869 // Trigger asynchronous package population from the web-app 870 { 871 AutoLocker<BLocker> lock(&fPackageToPopulateLock); 872 fPackageToPopulate = package; 873 } 874 release_sem_etc(fPackageToPopulateSem, 1, 0); 875 } 876 877 878 void 879 MainWindow::_ClearPackage() 880 { 881 fPackageInfoView->Clear(); 882 } 883 884 885 void 886 MainWindow::_RefreshRepositories(bool force) 887 { 888 if (fSinglePackageMode) 889 return; 890 891 BPackageRoster roster; 892 BStringList repositoryNames; 893 894 status_t result = roster.GetRepositoryNames(repositoryNames); 895 if (result != B_OK) 896 return; 897 898 DecisionProvider decisionProvider; 899 JobStateListener listener; 900 BContext context(decisionProvider, listener); 901 902 BRepositoryCache cache; 903 for (int32 i = 0; i < repositoryNames.CountStrings(); ++i) { 904 const BString& repoName = repositoryNames.StringAt(i); 905 BRepositoryConfig repoConfig; 906 result = roster.GetRepositoryConfig(repoName, &repoConfig); 907 if (result != B_OK) { 908 // TODO: notify user 909 continue; 910 } 911 912 if (roster.GetRepositoryCache(repoName, &cache) != B_OK || force) { 913 try { 914 BRefreshRepositoryRequest refreshRequest(context, repoConfig); 915 916 result = refreshRequest.Process(); 917 } catch (BFatalErrorException ex) { 918 BString message(B_TRANSLATE("An error occurred while " 919 "refreshing the repository: %error% (%details%)")); 920 message.ReplaceFirst("%error%", ex.Message()); 921 message.ReplaceFirst("%details%", ex.Details()); 922 _NotifyUser("Error", message.String()); 923 } catch (BException ex) { 924 BString message(B_TRANSLATE("An error occurred while " 925 "refreshing the repository: %error%")); 926 message.ReplaceFirst("%error%", ex.Message()); 927 _NotifyUser("Error", message.String()); 928 } 929 } 930 } 931 } 932 933 934 void 935 MainWindow::_RefreshPackageList(bool force) 936 { 937 if (fSinglePackageMode) 938 return; 939 940 BPackageRoster roster; 941 BStringList repositoryNames; 942 943 status_t result = roster.GetRepositoryNames(repositoryNames); 944 if (result != B_OK) 945 return; 946 947 DepotInfoMap depots; 948 for (int32 i = 0; i < repositoryNames.CountStrings(); i++) { 949 const BString& repoName = repositoryNames.StringAt(i); 950 DepotInfo depotInfo = DepotInfo(repoName); 951 952 BRepositoryConfig repoConfig; 953 status_t getRepositoryConfigStatus = roster.GetRepositoryConfig( 954 repoName, &repoConfig); 955 956 if (getRepositoryConfigStatus == B_OK) { 957 depotInfo.SetBaseURL(repoConfig.BaseURL()); 958 } else { 959 printf("unable to obtain the repository config for local " 960 "repository '%s'; %s\n", 961 repoName.String(), strerror(getRepositoryConfigStatus)); 962 } 963 964 depots[repoName] = depotInfo; 965 } 966 967 PackageManager manager(B_PACKAGE_INSTALLATION_LOCATION_HOME); 968 try { 969 manager.Init(PackageManager::B_ADD_INSTALLED_REPOSITORIES 970 | PackageManager::B_ADD_REMOTE_REPOSITORIES); 971 } catch (BException ex) { 972 BString message(B_TRANSLATE("An error occurred while " 973 "initializing the package manager: %message%")); 974 message.ReplaceFirst("%message%", ex.Message()); 975 _NotifyUser("Error", message.String()); 976 return; 977 } 978 979 BObjectList<BSolverPackage> packages; 980 result = manager.Solver()->FindPackages("", 981 BSolver::B_FIND_CASE_INSENSITIVE | BSolver::B_FIND_IN_NAME 982 | BSolver::B_FIND_IN_SUMMARY | BSolver::B_FIND_IN_DESCRIPTION 983 | BSolver::B_FIND_IN_PROVIDES, 984 packages); 985 if (result != B_OK) { 986 BString message(B_TRANSLATE("An error occurred while " 987 "obtaining the package list: %message%")); 988 message.ReplaceFirst("%message%", strerror(result)); 989 _NotifyUser("Error", message.String()); 990 return; 991 } 992 993 if (packages.IsEmpty()) 994 return; 995 996 PackageInfoMap foundPackages; 997 // if a given package is installed locally, we will potentially 998 // get back multiple entries, one for each local installation 999 // location, and one for each remote repository the package 1000 // is available in. The above map is used to ensure that in such 1001 // cases we consolidate the information, rather than displaying 1002 // duplicates 1003 PackageInfoMap remotePackages; 1004 // any package that we find in a remote repository goes in this map. 1005 // this is later used to discern which packages came from a local 1006 // installation only, as those must be handled a bit differently 1007 // upon uninstallation, since we'd no longer be able to pull them 1008 // down remotely. 1009 BStringList systemFlaggedPackages; 1010 // any packages flagged as a system package are added to this list. 1011 // such packages cannot be uninstalled, nor can any of their deps. 1012 PackageInfoMap systemInstalledPackages; 1013 // any packages installed in system are added to this list. 1014 // This is later used for dependency resolution of the actual 1015 // system packages in order to compute the list of protected 1016 // dependencies indicated above. 1017 1018 for (int32 i = 0; i < packages.CountItems(); i++) { 1019 BSolverPackage* package = packages.ItemAt(i); 1020 const BPackageInfo& repoPackageInfo = package->Info(); 1021 const BString repositoryName = package->Repository()->Name(); 1022 PackageInfoRef modelInfo; 1023 PackageInfoMap::iterator it = foundPackages.find( 1024 repoPackageInfo.Name()); 1025 if (it != foundPackages.end()) 1026 modelInfo.SetTo(it->second); 1027 else { 1028 // Add new package info 1029 modelInfo.SetTo(new(std::nothrow) PackageInfo(repoPackageInfo), 1030 true); 1031 1032 if (modelInfo.Get() == NULL) 1033 return; 1034 1035 modelInfo->SetDepotName(repositoryName); 1036 1037 foundPackages[repoPackageInfo.Name()] = modelInfo; 1038 } 1039 1040 modelInfo->AddListener(this); 1041 1042 BSolverRepository* repository = package->Repository(); 1043 if (dynamic_cast<BPackageManager::RemoteRepository*>(repository) 1044 != NULL) { 1045 depots[repository->Name()].AddPackage(modelInfo); 1046 remotePackages[modelInfo->Name()] = modelInfo; 1047 } else { 1048 if (repository == static_cast<const BSolverRepository*>( 1049 manager.SystemRepository())) { 1050 modelInfo->AddInstallationLocation( 1051 B_PACKAGE_INSTALLATION_LOCATION_SYSTEM); 1052 if (!modelInfo->IsSystemPackage()) { 1053 systemInstalledPackages[repoPackageInfo.FileName()] 1054 = modelInfo; 1055 } 1056 } else if (repository == static_cast<const BSolverRepository*>( 1057 manager.HomeRepository())) { 1058 modelInfo->AddInstallationLocation( 1059 B_PACKAGE_INSTALLATION_LOCATION_HOME); 1060 } 1061 } 1062 1063 if (modelInfo->IsSystemPackage()) 1064 systemFlaggedPackages.Add(repoPackageInfo.FileName()); 1065 } 1066 1067 bool wasEmpty = fModel.Depots().IsEmpty(); 1068 if (force || wasEmpty) 1069 fBulkLoadStateMachine.Stop(); 1070 1071 BAutolock lock(fModel.Lock()); 1072 1073 if (force) 1074 fModel.Clear(); 1075 1076 // filter remote packages from the found list 1077 // any packages remaining will be locally installed packages 1078 // that weren't acquired from a repository 1079 for (PackageInfoMap::iterator it = remotePackages.begin(); 1080 it != remotePackages.end(); it++) { 1081 foundPackages.erase(it->first); 1082 } 1083 1084 if (!foundPackages.empty()) { 1085 BString repoName = B_TRANSLATE("Local"); 1086 depots[repoName] = DepotInfo(repoName); 1087 DepotInfoMap::iterator depot = depots.find(repoName); 1088 for (PackageInfoMap::iterator it = foundPackages.begin(); 1089 it != foundPackages.end(); ++it) { 1090 depot->second.AddPackage(it->second); 1091 } 1092 } 1093 1094 for (DepotInfoMap::iterator it = depots.begin(); it != depots.end(); it++) { 1095 if (fModel.HasDepot(it->second.Name())) 1096 fModel.SyncDepot(it->second); 1097 else 1098 fModel.AddDepot(it->second); 1099 } 1100 1101 // start retrieving package icons and average ratings 1102 if (force || wasEmpty) { 1103 fBulkLoadStateMachine.Start(); 1104 } 1105 1106 // compute the OS package dependencies 1107 try { 1108 // create the solver 1109 BSolver* solver; 1110 status_t error = BSolver::Create(solver); 1111 if (error != B_OK) 1112 throw BFatalErrorException(error, "Failed to create solver."); 1113 1114 ObjectDeleter<BSolver> solverDeleter(solver); 1115 BPath systemPath; 1116 error = find_directory(B_SYSTEM_PACKAGES_DIRECTORY, &systemPath); 1117 if (error != B_OK) { 1118 throw BFatalErrorException(error, 1119 "Unable to retrieve system packages directory."); 1120 } 1121 1122 // add the "installed" repository with the given packages 1123 BSolverRepository installedRepository; 1124 { 1125 BRepositoryBuilder installedRepositoryBuilder(installedRepository, 1126 "installed"); 1127 for (int32 i = 0; i < systemFlaggedPackages.CountStrings(); i++) { 1128 BPath packagePath(systemPath); 1129 packagePath.Append(systemFlaggedPackages.StringAt(i)); 1130 installedRepositoryBuilder.AddPackage(packagePath.Path()); 1131 } 1132 installedRepositoryBuilder.AddToSolver(solver, true); 1133 } 1134 1135 // add system repository 1136 BSolverRepository systemRepository; 1137 { 1138 BRepositoryBuilder systemRepositoryBuilder(systemRepository, 1139 "system"); 1140 for (PackageInfoMap::iterator it = systemInstalledPackages.begin(); 1141 it != systemInstalledPackages.end(); it++) { 1142 BPath packagePath(systemPath); 1143 packagePath.Append(it->first); 1144 systemRepositoryBuilder.AddPackage(packagePath.Path()); 1145 } 1146 systemRepositoryBuilder.AddToSolver(solver, false); 1147 } 1148 1149 // solve 1150 error = solver->VerifyInstallation(); 1151 if (error != B_OK) { 1152 throw BFatalErrorException(error, "Failed to compute packages to " 1153 "install."); 1154 } 1155 1156 BSolverResult solverResult; 1157 error = solver->GetResult(solverResult); 1158 if (error != B_OK) { 1159 throw BFatalErrorException(error, "Failed to retrieve system " 1160 "package dependency list."); 1161 } 1162 1163 for (int32 i = 0; const BSolverResultElement* element 1164 = solverResult.ElementAt(i); i++) { 1165 BSolverPackage* package = element->Package(); 1166 if (element->Type() == BSolverResultElement::B_TYPE_INSTALL) { 1167 PackageInfoMap::iterator it = systemInstalledPackages.find( 1168 package->Info().FileName()); 1169 if (it != systemInstalledPackages.end()) 1170 it->second->SetSystemDependency(true); 1171 } 1172 } 1173 } catch (BFatalErrorException ex) { 1174 printf("Fatal exception occurred while resolving system dependencies: " 1175 "%s, details: %s\n", strerror(ex.Error()), ex.Details().String()); 1176 } catch (BNothingToDoException) { 1177 // do nothing 1178 } catch (BException ex) { 1179 printf("Exception occurred while resolving system dependencies: %s\n", 1180 ex.Message().String()); 1181 } catch (...) { 1182 printf("Unknown exception occurred while resolving system " 1183 "dependencies.\n"); 1184 } 1185 } 1186 1187 1188 void 1189 MainWindow::_StartRefreshWorker(bool force) 1190 { 1191 if (fModelWorker != B_BAD_THREAD_ID) 1192 return; 1193 1194 RefreshWorkerParameters* parameters = new(std::nothrow) 1195 RefreshWorkerParameters(this, force); 1196 if (parameters == NULL) 1197 return; 1198 1199 fWorkStatusView->SetBusy(B_TRANSLATE("Refreshing...")); 1200 1201 ObjectDeleter<RefreshWorkerParameters> deleter(parameters); 1202 fModelWorker = spawn_thread(&_RefreshModelThreadWorker, "model loader", 1203 B_LOW_PRIORITY, parameters); 1204 1205 if (fModelWorker > 0) { 1206 deleter.Detach(); 1207 resume_thread(fModelWorker); 1208 } 1209 } 1210 1211 1212 status_t 1213 MainWindow::_RefreshModelThreadWorker(void* arg) 1214 { 1215 RefreshWorkerParameters* parameters 1216 = reinterpret_cast<RefreshWorkerParameters*>(arg); 1217 MainWindow* mainWindow = parameters->window; 1218 ObjectDeleter<RefreshWorkerParameters> deleter(parameters); 1219 1220 BMessenger messenger(mainWindow); 1221 1222 mainWindow->_RefreshRepositories(parameters->forceRefresh); 1223 1224 if (mainWindow->fTerminating) 1225 return B_OK; 1226 1227 mainWindow->_RefreshPackageList(parameters->forceRefresh); 1228 1229 messenger.SendMessage(MSG_MODEL_WORKER_DONE); 1230 1231 return B_OK; 1232 } 1233 1234 1235 status_t 1236 MainWindow::_PackageActionWorker(void* arg) 1237 { 1238 MainWindow* window = reinterpret_cast<MainWindow*>(arg); 1239 1240 while (acquire_sem(window->fPendingActionsSem) == B_OK) { 1241 PackageActionRef ref; 1242 { 1243 AutoLocker<BLocker> lock(&window->fPendingActionsLock); 1244 ref = window->fPendingActions.ItemAt(0); 1245 if (ref.Get() == NULL) 1246 break; 1247 window->fPendingActions.Remove(0); 1248 } 1249 1250 BMessenger messenger(window); 1251 BMessage busyMessage(MSG_PACKAGE_WORKER_BUSY); 1252 BString text(ref->Label()); 1253 text << "..."; 1254 busyMessage.AddString("reason", text); 1255 1256 messenger.SendMessage(&busyMessage); 1257 ref->Perform(); 1258 messenger.SendMessage(MSG_PACKAGE_WORKER_IDLE); 1259 } 1260 1261 return 0; 1262 } 1263 1264 1265 status_t 1266 MainWindow::_PopulatePackageWorker(void* arg) 1267 { 1268 MainWindow* window = reinterpret_cast<MainWindow*>(arg); 1269 1270 while (acquire_sem(window->fPackageToPopulateSem) == B_OK) { 1271 PackageInfoRef package; 1272 { 1273 AutoLocker<BLocker> lock(&window->fPackageToPopulateLock); 1274 package = window->fPackageToPopulate; 1275 } 1276 1277 if (package.Get() != NULL) { 1278 window->fModel.PopulatePackage(package, 1279 Model::POPULATE_USER_RATINGS | Model::POPULATE_SCREEN_SHOTS 1280 | Model::POPULATE_CHANGELOG); 1281 } 1282 } 1283 1284 return 0; 1285 } 1286 1287 1288 /* static */ status_t 1289 MainWindow::_PackagesToShowWorker(void* arg) 1290 { 1291 MainWindow* window = reinterpret_cast<MainWindow*>(arg); 1292 1293 while (acquire_sem(window->fNewPackagesToShowSem) == B_OK) { 1294 PackageList packageList; 1295 int32 listID = 0; 1296 { 1297 AutoLocker<BLocker> lock(&window->fPackagesToShowListLock); 1298 packageList = window->fPackagesToShowList; 1299 listID = atomic_get(&window->fPackagesToShowListID); 1300 window->fPackagesToShowList.Clear(); 1301 } 1302 1303 // Add packages to list views in batches of kPackagesPerUpdate so we 1304 // don't block the window thread for long with each iteration 1305 enum { 1306 kPackagesPerUpdate = 20 1307 }; 1308 uint32 packagesInMessage = 0; 1309 BMessage message(MSG_ADD_VISIBLE_PACKAGES); 1310 BMessenger messenger(window); 1311 bool listIsOutdated = false; 1312 1313 for (int i = 0; i < packageList.CountItems(); i++) { 1314 const PackageInfoRef& package = packageList.ItemAtFast(i); 1315 1316 if (packagesInMessage >= kPackagesPerUpdate) { 1317 if (listID != atomic_get(&window->fPackagesToShowListID)) { 1318 // The model was changed again in the meantime, and thus 1319 // our package list isn't current anymore. Send no further 1320 // messags based on this list and go back to start. 1321 listIsOutdated = true; 1322 break; 1323 } 1324 1325 message.AddInt32("list_id", listID); 1326 messenger.SendMessage(&message); 1327 message.MakeEmpty(); 1328 packagesInMessage = 0; 1329 1330 // Don't spam the window's message queue, which would make it 1331 // unresponsive (i.e. allows UI messages to get in between our 1332 // messages). When it has processed the message we just sent, 1333 // it will let us know by releasing the semaphore. 1334 acquire_sem(window->fShowPackagesAcknowledgeSem); 1335 } 1336 package->AcquireReference(); 1337 message.AddPointer("package_ref", package.Get()); 1338 packagesInMessage++; 1339 } 1340 1341 if (listIsOutdated) 1342 continue; 1343 1344 // Send remaining package infos, if any, which didn't make it into 1345 // the last message (count < kPackagesPerUpdate) 1346 if (packagesInMessage > 0) { 1347 message.AddInt32("list_id", listID); 1348 messenger.SendMessage(&message); 1349 acquire_sem(window->fShowPackagesAcknowledgeSem); 1350 } 1351 1352 // Update selected package in list views 1353 messenger.SendMessage(MSG_UPDATE_SELECTED_PACKAGE); 1354 } 1355 1356 return 0; 1357 } 1358 1359 1360 void 1361 MainWindow::_NotifyUser(const char* title, const char* message) 1362 { 1363 BAlert* alert = new(std::nothrow) BAlert(title, message, 1364 B_TRANSLATE("Close")); 1365 1366 if (alert != NULL) 1367 alert->Go(); 1368 } 1369 1370 1371 void 1372 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage) 1373 { 1374 UserLoginWindow* window = new UserLoginWindow(this, 1375 BRect(0, 0, 500, 400), fModel); 1376 1377 if (onSuccessMessage.what != 0) 1378 window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage); 1379 1380 window->Show(); 1381 } 1382 1383 1384 void 1385 MainWindow::_UpdateAuthorization() 1386 { 1387 BString username(fModel.Username()); 1388 bool hasUser = !username.IsEmpty(); 1389 1390 if (fLogOutItem != NULL) 1391 fLogOutItem->SetEnabled(hasUser); 1392 if (fLogInItem != NULL) { 1393 if (hasUser) 1394 fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS)); 1395 else 1396 fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS)); 1397 } 1398 1399 if (fUserMenu != NULL) { 1400 BString label; 1401 if (username.Length() == 0) { 1402 label = B_TRANSLATE("Not logged in"); 1403 } else { 1404 label = B_TRANSLATE("Logged in as %User%"); 1405 label.ReplaceAll("%User%", username); 1406 } 1407 fUserMenu->Superitem()->SetLabel(label); 1408 } 1409 } 1410 1411 1412 void 1413 MainWindow::_RatePackage() 1414 { 1415 if (fModel.Username().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)); 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