xref: /haiku/src/servers/package/ProblemWindow.cpp (revision 04a0e9c7b68cbe3a43d38e2bca8e860fd80936fb)
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 	rgb_color evenBackground = ui_color(B_LIST_BACKGROUND_COLOR);
214 	rgb_color oddBackground = tint_color(evenBackground, 1.04);
215 
216 	int32 problemCount = solver->CountProblems();
217 	for (int32 i = 0; i < problemCount; i++) {
218 		_AddProblem(solver->ProblemAt(i),
219 			(i & 1) == 0 ? evenBackground : oddBackground);
220 	}
221 }
222 
223 
224 void
225 ProblemWindow::_AddProblem(BSolverProblem* problem,
226 	const rgb_color& backgroundColor)
227 {
228 	BGroupView* problemGroup = new BGroupView(B_VERTICAL);
229 	fContainerView->AddChild(problemGroup);
230 	problemGroup->GroupLayout()->SetInsets(B_USE_SMALL_INSETS);
231 	problemGroup->SetViewColor(backgroundColor);
232 
233 	BStringView* problemView = new BStringView(NULL, problem->ToString());
234 	problemGroup->AddChild(problemView);
235 	BFont problemFont;
236 	problemView->GetFont(&problemFont);
237 	problemFont.SetFace(B_BOLD_FACE);
238 	problemView->SetFont(&problemFont);
239 
240 	int32 solutionCount = problem->CountSolutions();
241 	for (int32 k = 0; k < solutionCount; k++) {
242 		const BSolverProblemSolution* solution = problem->SolutionAt(k);
243 		BRadioButton* solutionButton = new BRadioButton(
244 			BString().SetToFormat("solution %" B_PRId32 ":", k + 1),
245 			new BMessage(kUpdateRetryButtonMessage));
246 		problemGroup->AddChild(solutionButton);
247 
248 		BGroupLayout* elementsGroup = new BGroupLayout(B_VERTICAL);
249 		problemGroup->AddChild(elementsGroup);
250 		elementsGroup->SetInsets(20, 0, 0, 0);
251 
252 		int32 elementCount = solution->CountElements();
253 		for (int32 l = 0; l < elementCount; l++) {
254 			const BSolverProblemSolutionElement* element
255 				= solution->ElementAt(l);
256 			BStringView* elementView = new BStringView(NULL,
257 				BString().SetToFormat("- %s",
258 					_SolutionElementText(element).String()));
259 			elementsGroup->AddView(elementView);
260 		}
261 
262 		fSolutions[solutionButton] = Solution(problem, solution);
263 	}
264 
265 	BRadioButton* ignoreButton = new BRadioButton("ignore problem for now",
266 		new BMessage(kUpdateRetryButtonMessage));
267 	problemGroup->AddChild(ignoreButton);
268 	ignoreButton->SetValue(B_CONTROL_ON);
269 }
270 
271 
272 BString
273 ProblemWindow::_SolutionElementText(
274 	const BSolverProblemSolutionElement* element) const
275 {
276 	// Reword text for B_ALLOW_DEINSTALLATION, if the package has been added
277 	// by the user.
278 	BSolverPackage* package = element->SourcePackage();
279 	if (element->Type() == BSolverProblemSolutionElement::B_ALLOW_DEINSTALLATION
280 		&& package != NULL
281 		&& fPackagesAddedByUser->find(package) != fPackagesAddedByUser->end()) {
282 		return BString("don't activate package %source%").ReplaceAll(
283 			"%source%", package->VersionedName());
284 	}
285 
286 	return element->ToString();
287 }
288 
289 
290 bool
291 ProblemWindow::_AnySolutionSelected() const
292 {
293 	for (SolutionMap::const_iterator it = fSolutions.begin();
294 		it != fSolutions.end(); ++it) {
295 		BRadioButton* button = it->first;
296 		if (button->Value() == B_CONTROL_ON)
297 			return true;
298 	}
299 
300 	return false;
301 }
302