1 /* 2 * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "ProblemWindow.h" 8 9 #include <Button.h> 10 #include <GroupView.h> 11 #include <LayoutBuilder.h> 12 #include <RadioButton.h> 13 #include <ScrollView.h> 14 #include <StringView.h> 15 #include <package/solver/Solver.h> 16 #include <package/solver/SolverPackage.h> 17 #include <package/solver/SolverProblem.h> 18 #include <package/solver/SolverProblemSolution.h> 19 20 #include <AutoLocker.h> 21 #include <package/manager/Exceptions.h> 22 #include <ViewPort.h> 23 24 25 using namespace BPackageKit; 26 27 using BPackageKit::BManager::BPrivate::BFatalErrorException; 28 29 30 static const uint32 kRetryMessage = 'rtry'; 31 static const uint32 kUpdateRetryButtonMessage = 'uprt'; 32 33 34 struct ProblemWindow::Solution { 35 BSolverProblem* fProblem; 36 const BSolverProblemSolution* fSolution; 37 38 Solution() 39 : 40 fProblem(NULL), 41 fSolution(NULL) 42 { 43 } 44 45 Solution(BSolverProblem* problem, const BSolverProblemSolution* solution) 46 : 47 fProblem(problem), 48 fSolution(solution) 49 { 50 } 51 }; 52 53 54 ProblemWindow::ProblemWindow() 55 : 56 BWindow(BRect(0, 0, 400, 300), "Package problems", B_TITLED_WINDOW_LOOK, 57 B_NORMAL_WINDOW_FEEL, 58 B_ASYNCHRONOUS_CONTROLS | B_NOT_MINIMIZABLE | B_AUTO_UPDATE_SIZE_LIMITS, 59 B_ALL_WORKSPACES), 60 fDoneSemaphore(-1), 61 fClientWaiting(false), 62 fAccepted(false), 63 fContainerView(NULL), 64 fCancelButton(NULL), 65 fRetryButton(NULL), 66 fSolutions(), 67 fPackagesAddedByUser(NULL), 68 fPackagesRemovedByUser(NULL) 69 70 { 71 fDoneSemaphore = create_sem(0, "package problems"); 72 if (fDoneSemaphore < 0) 73 throw std::bad_alloc(); 74 75 BStringView* topTextView = NULL; 76 BViewPort* viewPort = NULL; 77 78 BLayoutBuilder::Group<>(this, B_VERTICAL, B_USE_DEFAULT_SPACING) 79 .SetInsets(B_USE_SMALL_INSETS) 80 .Add(topTextView = new BStringView(NULL, 81 "The following problems have been encountered. Please select a " 82 "solution for each:")) 83 .Add(new BScrollView(NULL, viewPort = new BViewPort(), 0, false, true)) 84 .AddGroup(B_HORIZONTAL) 85 .Add(fCancelButton = new BButton("Cancel", new BMessage(B_CANCEL))) 86 .AddGlue() 87 .Add(fRetryButton = new BButton("Retry", 88 new BMessage(kRetryMessage))) 89 .End(); 90 91 topTextView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 92 93 viewPort->SetChildView(fContainerView = new BGroupView(B_VERTICAL, 0)); 94 95 // set small scroll step (large step will be set by the view port) 96 font_height fontHeight; 97 topTextView->GetFontHeight(&fontHeight); 98 float smallStep = ceilf(fontHeight.ascent + fontHeight.descent); 99 viewPort->ScrollBar(B_VERTICAL)->SetSteps(smallStep, smallStep); 100 } 101 102 103 ProblemWindow::~ProblemWindow() 104 { 105 if (fDoneSemaphore >= 0) 106 delete_sem(fDoneSemaphore); 107 } 108 109 110 bool 111 ProblemWindow::Go(BSolver* solver, const SolverPackageSet& packagesAddedByUser, 112 const SolverPackageSet& packagesRemovedByUser) 113 { 114 AutoLocker<ProblemWindow> locker(this); 115 116 fPackagesAddedByUser = &packagesAddedByUser; 117 fPackagesRemovedByUser = &packagesRemovedByUser; 118 119 _ClearProblemsGui(); 120 _AddProblemsGui(solver); 121 122 fCancelButton->SetEnabled(true); 123 fRetryButton->SetEnabled(false); 124 125 if (IsHidden()) { 126 CenterOnScreen(); 127 Show(); 128 } 129 130 fAccepted = false; 131 fClientWaiting = true; 132 133 locker.Unlock(); 134 135 while (acquire_sem(fDoneSemaphore) == B_INTERRUPTED) { 136 } 137 138 locker.Lock(); 139 if (!locker.IsLocked() || !fAccepted || !_AnySolutionSelected()) 140 return false; 141 142 // set the solutions 143 for (SolutionMap::const_iterator it = fSolutions.begin(); 144 it != fSolutions.end(); ++it) { 145 BRadioButton* button = it->first; 146 if (button->Value() == B_CONTROL_ON) { 147 const Solution& solution = it->second; 148 status_t error = solver->SelectProblemSolution(solution.fProblem, 149 solution.fSolution); 150 if (error != B_OK) 151 throw BFatalErrorException(error, "failed to set solution"); 152 } 153 } 154 155 return true; 156 } 157 158 159 bool 160 ProblemWindow::QuitRequested() 161 { 162 if (fClientWaiting) { 163 fClientWaiting = false; 164 release_sem(fDoneSemaphore); 165 } 166 return true; 167 } 168 169 170 void 171 ProblemWindow::MessageReceived(BMessage* message) 172 { 173 switch (message->what) { 174 case B_CANCEL: 175 Hide(); 176 fClientWaiting = false; 177 release_sem(fDoneSemaphore); 178 break; 179 case kRetryMessage: 180 fCancelButton->SetEnabled(false); 181 fRetryButton->SetEnabled(false); 182 fAccepted = true; 183 fClientWaiting = false; 184 release_sem(fDoneSemaphore); 185 break; 186 case kUpdateRetryButtonMessage: 187 fRetryButton->SetEnabled(_AnySolutionSelected()); 188 break; 189 default: 190 BWindow::MessageReceived(message); 191 break; 192 } 193 } 194 195 196 void 197 ProblemWindow::_ClearProblemsGui() 198 { 199 fSolutions.clear(); 200 201 int32 count = fContainerView->CountChildren(); 202 for (int32 i = count - 1; i >= 0; i--) { 203 BView* child = fContainerView->ChildAt(i); 204 fContainerView->RemoveChild(child); 205 delete child; 206 } 207 } 208 209 210 void 211 ProblemWindow::_AddProblemsGui(BSolver* solver) 212 { 213 int32 problemCount = solver->CountProblems(); 214 for (int32 i = 0; i < problemCount; i++) { 215 _AddProblem(solver->ProblemAt(i), 216 (i & 1) == 0 ? B_NO_TINT : 1.04); 217 } 218 } 219 220 221 void 222 ProblemWindow::_AddProblem(BSolverProblem* problem, 223 const float backgroundTint) 224 { 225 BGroupView* problemGroup = new BGroupView(B_VERTICAL); 226 fContainerView->AddChild(problemGroup); 227 problemGroup->GroupLayout()->SetInsets(B_USE_SMALL_INSETS); 228 problemGroup->SetViewUIColor(B_LIST_BACKGROUND_COLOR, backgroundTint); 229 230 BStringView* problemView = new BStringView(NULL, problem->ToString()); 231 problemGroup->AddChild(problemView); 232 BFont problemFont; 233 problemView->GetFont(&problemFont); 234 problemFont.SetFace(B_BOLD_FACE); 235 problemView->SetFont(&problemFont); 236 problemView->AdoptParentColors(); 237 238 int32 solutionCount = problem->CountSolutions(); 239 for (int32 k = 0; k < solutionCount; k++) { 240 const BSolverProblemSolution* solution = problem->SolutionAt(k); 241 BRadioButton* solutionButton = new BRadioButton( 242 BString().SetToFormat("solution %" B_PRId32 ":", k + 1), 243 new BMessage(kUpdateRetryButtonMessage)); 244 problemGroup->AddChild(solutionButton); 245 246 BGroupLayout* elementsGroup = new BGroupLayout(B_VERTICAL); 247 problemGroup->AddChild(elementsGroup); 248 elementsGroup->SetInsets(20, 0, 0, 0); 249 250 int32 elementCount = solution->CountElements(); 251 for (int32 l = 0; l < elementCount; l++) { 252 const BSolverProblemSolutionElement* element 253 = solution->ElementAt(l); 254 BStringView* elementView = new BStringView(NULL, 255 BString().SetToFormat("- %s", 256 _SolutionElementText(element).String())); 257 elementsGroup->AddView(elementView); 258 elementView->AdoptParentColors(); 259 } 260 261 fSolutions[solutionButton] = Solution(problem, solution); 262 } 263 264 BRadioButton* ignoreButton = new BRadioButton("ignore problem for now", 265 new BMessage(kUpdateRetryButtonMessage)); 266 problemGroup->AddChild(ignoreButton); 267 ignoreButton->SetValue(B_CONTROL_ON); 268 } 269 270 271 BString 272 ProblemWindow::_SolutionElementText( 273 const BSolverProblemSolutionElement* element) const 274 { 275 // Reword text for B_ALLOW_DEINSTALLATION, if the package has been added 276 // by the user. 277 BSolverPackage* package = element->SourcePackage(); 278 if (element->Type() == BSolverProblemSolutionElement::B_ALLOW_DEINSTALLATION 279 && package != NULL 280 && fPackagesAddedByUser->find(package) != fPackagesAddedByUser->end()) { 281 return BString("don't activate package %source%").ReplaceAll( 282 "%source%", package->VersionedName()); 283 } 284 285 return element->ToString(); 286 } 287 288 289 bool 290 ProblemWindow::_AnySolutionSelected() const 291 { 292 for (SolutionMap::const_iterator it = fSolutions.begin(); 293 it != fSolutions.end(); ++it) { 294 BRadioButton* button = it->first; 295 if (button->Value() == B_CONTROL_ON) 296 return true; 297 } 298 299 return false; 300 } 301