xref: /haiku/src/apps/haikudepot/ui/UserUsageConditionsWindow.cpp (revision a72f3582be00f2151800fa7da036d7adc14e3272)
1 /*
2  * Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 #include "UserUsageConditionsWindow.h"
7 
8 #include <Button.h>
9 #include <Catalog.h>
10 #include <Font.h>
11 #include <LayoutBuilder.h>
12 #include <ScrollView.h>
13 #include <StringView.h>
14 #include <TextView.h>
15 
16 #include "AppUtils.h"
17 #include "BarberPole.h"
18 #include "HaikuDepotConstants.h"
19 #include "Logger.h"
20 #include "MarkupTextView.h"
21 #include "Model.h"
22 #include "UserUsageConditions.h"
23 #include "WebAppInterface.h"
24 
25 
26 #undef B_TRANSLATION_CONTEXT
27 #define B_TRANSLATION_CONTEXT "UserUsageConditions"
28 
29 #define PLACEHOLDER_TEXT "..."
30 
31 #define INTRODUCTION_TEXT_LATEST "HaikuDepot communicates with a " \
32 	"sever component called HaikuDepotServer.  These are the latest user " \
33 	"usage conditions for use of the HaikuDepotServer service."
34 
35 #define INTRODUCTION_TEXT_USER "HaikuDepot communicates with a " \
36 	"sever component called HaikuDepotServer.  These are the user usage " \
37 	"conditions that the user has agreed to in relation to the use of the " \
38 	"HaikuDepotServer service."
39 
40 /*!	This is the anticipated number of lines of test that appear in the
41 	introduction.
42 */
43 
44 #define LINES_INTRODUCTION_TEXT 2
45 
46 
47 enum {
48 	MSG_USER_USAGE_CONDITIONS_DATA	= 'uucd',
49 	MSG_USER_USAGE_CONDITIONS_ERROR	= 'uuce'
50 };
51 
52 
53 UserUsageConditionsWindow::UserUsageConditionsWindow(BWindow* parent,
54 	BRect frame, Model& model, UserUsageConditionsSelectionMode mode)
55 	:
56 	BWindow(frame, B_TRANSLATE("User Usage Conditions"),
57 			B_FLOATING_WINDOW_LOOK, B_FLOATING_SUBSET_WINDOW_FEEL,
58 			B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS
59 				| B_NOT_RESIZABLE | B_NOT_ZOOMABLE),
60 	fMode(mode),
61 	fModel(model),
62 	fWorkerThread(-1)
63 {
64 	AddToSubset(parent);
65 
66 	if (mode != LATEST)
67 		debugger("only the LATEST user usage conditions are handled for now");
68 
69 	fWorkerIndicator = new BarberPole("fetch data worker indicator");
70 	BSize workerIndicatorSize;
71 	workerIndicatorSize.SetHeight(20);
72 	fWorkerIndicator->SetExplicitMinSize(workerIndicatorSize);
73 
74 	fCopyView = new MarkupTextView("copy view");
75 	fCopyView->SetViewUIColor(B_NO_COLOR);
76 	fCopyView->SetLowColor(RGB_COLOR_WHITE);
77 	fCopyView->SetInsets(8.0f);
78 
79 	BScrollView* scrollView = new BScrollView("copy scroll view", fCopyView,
80 		0, false, true, B_PLAIN_BORDER);
81 
82 	BTextView* introductionTextView = new BTextView("introduction text view");
83 	introductionTextView->AdoptSystemColors();
84 	introductionTextView->MakeEditable(false);
85 	introductionTextView->MakeSelectable(false);
86 	introductionTextView->SetText(B_TRANSLATE(_IntroductionTextForMode(mode)));
87 
88 	fAgeNoteStringView = new BStringView("age note string view",
89 		PLACEHOLDER_TEXT);
90 	fAgeNoteStringView->AdoptSystemColors();
91 	fAgeNoteStringView->SetExplicitMaxSize(
92 		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
93 
94 	BFont versionFont(be_plain_font);
95 	versionFont.SetSize(9.0);
96 
97 	fVersionStringView = new BStringView("version string view",
98 		PLACEHOLDER_TEXT);
99 	fVersionStringView->AdoptSystemColors();
100 	fVersionStringView->SetFont(&versionFont);
101 	fVersionStringView->SetAlignment(B_ALIGN_RIGHT);
102 	fVersionStringView->SetHighUIColor(B_PANEL_TEXT_COLOR, B_DARKEN_3_TINT);
103 	fVersionStringView->SetExplicitMaxSize(
104 		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
105 
106 	BSize introductionSize;
107 	introductionSize.SetHeight(
108 		_ExpectedIntroductionTextHeight(introductionTextView));
109 	introductionTextView->SetExplicitPreferredSize(introductionSize);
110 
111 	BButton* okButton = new BButton("ok", B_TRANSLATE("OK"),
112 		new BMessage(B_QUIT_REQUESTED));
113 
114 	BLayoutBuilder::Group<>(this, B_VERTICAL)
115 		.SetInsets(B_USE_WINDOW_INSETS)
116 		.Add(introductionTextView, 1)
117 		.AddGlue()
118 		.Add(fVersionStringView, 1)
119 		.Add(scrollView, 96)
120 		.Add(fAgeNoteStringView, 1)
121 		.AddGroup(B_HORIZONTAL, 1)
122 			.AddGlue()
123 			.Add(okButton)
124 			.End()
125 		.Add(fWorkerIndicator, 1)
126 		.End();
127 
128 	CenterIn(parent->Frame());
129 
130 	_FetchData();
131 		// start a new thread to pull down the user usage conditions data.
132 }
133 
134 
135 UserUsageConditionsWindow::~UserUsageConditionsWindow()
136 {
137 }
138 
139 
140 void
141 UserUsageConditionsWindow::MessageReceived(BMessage* message)
142 {
143 	switch (message->what) {
144 		case MSG_USER_USAGE_CONDITIONS_DATA:
145 		{
146 			UserUsageConditions data(message);
147 			_DisplayData(data);
148 			fWorkerIndicator->Stop();
149 			break;
150 		}
151 		default:
152 			BWindow::MessageReceived(message);
153 			break;
154 	}
155 }
156 
157 
158 bool
159 UserUsageConditionsWindow::QuitRequested()
160 {
161 	// for now we just don't allow the quit when the background thread
162 	// is processing.  In the future it would be good if the HTTP
163 	// requests were re-organized such that cancellations were easier to
164 	// implement.
165 
166 	if (fWorkerThread == -1)
167 		return true;
168 	if (Logger::IsInfoEnabled()) {
169 		fprintf(stderr, "unable to quit when the user usage "
170 			"conditions window is still fetching data\n");
171 	}
172 	return false;
173 }
174 
175 
176 /*!	This method is called on the main thread in order to initiate the background
177 	processing to obtain the user usage conditions data.  It will take
178 	responsibility for coordinating the creation of the thread and starting the
179 	thread etc...
180 */
181 
182 void
183 UserUsageConditionsWindow::_FetchData()
184 {
185 	if (-1 != fWorkerThread)
186 		debugger("illegal state - attempt to fetch, but fetch in progress");
187 	thread_id thread = spawn_thread(&_FetchDataThreadEntry,
188 		"Fetch user usage conditions data", B_NORMAL_PRIORITY, this);
189 	if (thread >= 0) {
190 		fWorkerIndicator->Start();
191 		_SetWorkerThread(thread);
192 		resume_thread(fWorkerThread);
193 	} else {
194 		debugger("unable to start a thread to fetch the user usage "
195 			"conditions.");
196 	}
197 }
198 
199 
200 /*!	This method is called from the thread in order to start the thread; it is
201 	the entry-point for the background processing to obtain the user usage
202 	conditions.
203 */
204 
205 /*static*/ int32
206 UserUsageConditionsWindow::_FetchDataThreadEntry(void* data)
207 {
208 	UserUsageConditionsWindow* win
209 		= reinterpret_cast<UserUsageConditionsWindow*>(data);
210 	win->_FetchDataPerform();
211 	return 0;
212 }
213 
214 
215 /*!	This method will perform the task of obtaining data about the user usage
216 	conditions.
217 */
218 
219 void
220 UserUsageConditionsWindow::_FetchDataPerform()
221 {
222 	UserUsageConditions conditions;
223 	WebAppInterface interface = fModel.GetWebAppInterface();
224 
225 	if (interface.RetrieveUserUsageConditions(NULL, conditions) == B_OK) {
226 		BMessage dataMessage(MSG_USER_USAGE_CONDITIONS_DATA);
227 		conditions.Archive(&dataMessage, true);
228 		BMessenger(this).SendMessage(&dataMessage);
229 	} else {
230 		AppUtils::NotifySimpleError(
231 			B_TRANSLATE("User Usage Conditions Download Problem"),
232 			B_TRANSLATE("An error has arisen downloading the user usage "
233 				"conditions.  Check the log for details and try again."));
234 		BMessenger(this).SendMessage(B_QUIT_REQUESTED);
235 	}
236 
237 	_SetWorkerThread(-1);
238 }
239 
240 
241 void
242 UserUsageConditionsWindow::_SetWorkerThread(thread_id thread)
243 {
244 	if (!Lock()) {
245 		if (Logger::IsInfoEnabled())
246 			fprintf(stderr, "failed to lock window\n");
247 	} else {
248 		fWorkerThread = thread;
249 		Unlock();
250 	}
251 }
252 
253 
254 void
255 UserUsageConditionsWindow::_DisplayData(const UserUsageConditions& data)
256 {
257 	fCopyView->SetText(data.CopyMarkdown());
258 	fAgeNoteStringView->SetText(_MinimumAgeText(data.MinimumAge()));
259 	fVersionStringView->SetText(_VersionText(data.Code()));
260 }
261 
262 
263 /*static*/ const BString
264 UserUsageConditionsWindow::_VersionText(const BString& code)
265 {
266 	BString versionText(
267 		B_TRANSLATE("Version %Code%"));
268 	versionText.ReplaceAll("%Code%", code);
269 	return versionText;
270 }
271 
272 
273 /*static*/ const BString
274 UserUsageConditionsWindow::_MinimumAgeText(uint8 minimumAge)
275 {
276 	BString minimumAgeString;
277 	minimumAgeString.SetToFormat("%" B_PRId8, minimumAge);
278 	BString ageNoteText(
279 		B_TRANSLATE("Users are required to be %AgeYears% years of age or "
280 			"older."));
281 	ageNoteText.ReplaceAll("%AgeYears%", minimumAgeString);
282 	return ageNoteText;
283 }
284 
285 
286 /*static*/ const BString
287 UserUsageConditionsWindow::_IntroductionTextForMode(
288 	UserUsageConditionsSelectionMode mode)
289 {
290 	switch (mode) {
291 		case LATEST:
292 			return INTRODUCTION_TEXT_LATEST;
293 		case USER:
294 			return INTRODUCTION_TEXT_USER;
295 		default:
296 			return "???";
297 	}
298 }
299 
300 
301 /*static*/ float
302 UserUsageConditionsWindow::_ExpectedIntroductionTextHeight(
303 	BTextView* introductionTextView)
304 {
305 	float insetTop;
306 	float insetBottom;
307 	introductionTextView->GetInsets(NULL, &insetTop, NULL, &insetBottom);
308 
309 	BSize introductionSize;
310 	font_height fh;
311 	be_plain_font->GetHeight(&fh);
312 	return ((fh.ascent + fh.descent + fh.leading) * LINES_INTRODUCTION_TEXT)
313 		+ insetTop + insetBottom;
314 }
315