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