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
29using namespace BPackageKit;
30
31using BPackageKit::BManager::BPrivate::BFatalErrorException;
32
33
34static const uint32 kRetryMessage = 'rtry';
35static const uint32 kUpdateRetryButtonMessage = 'uprt';
36
37
38struct 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
58ProblemWindow::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
109ProblemWindow::~ProblemWindow()
110{
111	if (fDoneSemaphore >= 0)
112		delete_sem(fDoneSemaphore);
113}
114
115
116bool
117ProblemWindow::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
168bool
169ProblemWindow::QuitRequested()
170{
171	if (fClientWaiting) {
172		fClientWaiting = false;
173		release_sem(fDoneSemaphore);
174	}
175	return true;
176}
177
178
179void
180ProblemWindow::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
205void
206ProblemWindow::_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
219void
220ProblemWindow::_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
230void
231ProblemWindow::_AddProblem(BSolverProblem* problem,
232	const float backgroundTint)
233{
234	BGroupView* problemGroup = new BGroupView(B_VERTICAL);
235	fContainerView->AddChild(problemGroup);
236	problemGroup->GroupLayout()->SetInsets(B_USE_SMALL_INSETS);
237	problemGroup->SetViewUIColor(B_LIST_BACKGROUND_COLOR, backgroundTint);
238
239	BStringView* problemView = new BStringView(NULL, problem->ToString());
240	problemGroup->AddChild(problemView);
241	BFont problemFont;
242	problemView->GetFont(&problemFont);
243	problemFont.SetFace(B_BOLD_FACE);
244	problemView->SetFont(&problemFont);
245	problemView->AdoptParentColors();
246
247	int32 solutionCount = problem->CountSolutions();
248	for (int k = 0; k < solutionCount; k++) {
249		const BSolverProblemSolution* solution = problem->SolutionAt(k);
250		BRadioButton* solutionButton = new BRadioButton(
251			BString().SetToFormat(B_TRANSLATE_COMMENT("solution %d:",
252				"Don't change the %d variable"), k + 1),
253			new BMessage(kUpdateRetryButtonMessage));
254		problemGroup->AddChild(solutionButton);
255
256		BGroupLayout* elementsGroup = new BGroupLayout(B_VERTICAL);
257		problemGroup->AddChild(elementsGroup);
258		elementsGroup->SetInsets(20, 0, 0, 0);
259
260		int32 elementCount = solution->CountElements();
261		for (int32 l = 0; l < elementCount; l++) {
262			const BSolverProblemSolutionElement* element
263				= solution->ElementAt(l);
264			BStringView* elementView = new BStringView(NULL,
265				BString().SetToFormat("- %s",
266					_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
281BString
282ProblemWindow::_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
300bool
301ProblemWindow::_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