xref: /haiku/src/servers/package/ProblemWindow.cpp (revision fc7456e9b1ec38c941134ed6d01c438cf289381e)
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 
42 	Solution()
43 		:
44 		fProblem(NULL),
45 		fSolution(NULL)
46 	{
47 	}
48 
49 	Solution(BSolverProblem* problem, const BSolverProblemSolution* solution)
50 		:
51 		fProblem(problem),
52 		fSolution(solution)
53 	{
54 	}
55 };
56 
57 
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 
109 ProblemWindow::~ProblemWindow()
110 {
111 	if (fDoneSemaphore >= 0)
112 		delete_sem(fDoneSemaphore);
113 }
114 
115 
116 bool
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
169 ProblemWindow::QuitRequested()
170 {
171 	if (fClientWaiting) {
172 		fClientWaiting = false;
173 		release_sem(fDoneSemaphore);
174 	}
175 	return true;
176 }
177 
178 
179 void
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
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
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
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
282 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
301 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