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