repository_infos: Enable automatic updates on the main Haiku repostiory.
[haiku.git] / src / apps / haikudepot / ui / RatePackageWindow.cpp
blob4906ac62ed2ebd06290119e97eb111aac92643f4
1 /*
2 * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
3 * Copyright 2016, Andrew Lindesay <apl@lindesay.co.nz>.
4 * All rights reserved. Distributed under the terms of the MIT License.
5 */
7 #include "RatePackageWindow.h"
9 #include <algorithm>
10 #include <stdio.h>
12 #include <Alert.h>
13 #include <Autolock.h>
14 #include <Catalog.h>
15 #include <Button.h>
16 #include <CheckBox.h>
17 #include <LayoutBuilder.h>
18 #include <MenuField.h>
19 #include <MenuItem.h>
20 #include <PopUpMenu.h>
21 #include <ScrollView.h>
22 #include <StringView.h>
24 #include "MarkupParser.h"
25 #include "RatingView.h"
26 #include "TextDocumentView.h"
27 #include "WebAppInterface.h"
30 #undef B_TRANSLATION_CONTEXT
31 #define B_TRANSLATION_CONTEXT "RatePackageWindow"
34 enum {
35 MSG_SEND = 'send',
36 MSG_PACKAGE_RATED = 'rpkg',
37 MSG_STABILITY_SELECTED = 'stbl',
38 MSG_LANGUAGE_SELECTED = 'lngs',
39 MSG_RATING_ACTIVE_CHANGED = 'rtac'
42 //! Layouts the scrollbar so it looks nice with no border and the document
43 // window look.
44 class ScrollView : public BScrollView {
45 public:
46 ScrollView(const char* name, BView* target)
48 BScrollView(name, target, 0, false, true, B_FANCY_BORDER)
52 virtual void DoLayout()
54 BRect innerFrame = Bounds();
55 innerFrame.InsetBy(2, 2);
57 BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
58 BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
60 if (vScrollBar != NULL)
61 innerFrame.right -= vScrollBar->Bounds().Width() - 1;
62 if (hScrollBar != NULL)
63 innerFrame.bottom -= hScrollBar->Bounds().Height() - 1;
65 BView* target = Target();
66 if (target != NULL) {
67 Target()->MoveTo(innerFrame.left, innerFrame.top);
68 Target()->ResizeTo(innerFrame.Width(), innerFrame.Height());
71 if (vScrollBar != NULL) {
72 BRect rect = innerFrame;
73 rect.left = rect.right + 1;
74 rect.right = rect.left + vScrollBar->Bounds().Width();
75 rect.top -= 1;
76 rect.bottom += 1;
78 vScrollBar->MoveTo(rect.left, rect.top);
79 vScrollBar->ResizeTo(rect.Width(), rect.Height());
82 if (hScrollBar != NULL) {
83 BRect rect = innerFrame;
84 rect.top = rect.bottom + 1;
85 rect.bottom = rect.top + hScrollBar->Bounds().Height();
86 rect.left -= 1;
87 rect.right += 1;
89 hScrollBar->MoveTo(rect.left, rect.top);
90 hScrollBar->ResizeTo(rect.Width(), rect.Height());
96 class SetRatingView : public RatingView {
97 public:
98 SetRatingView()
100 RatingView("rate package view"),
101 fPermanentRating(0.0f)
103 SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
104 SetRating(fPermanentRating);
107 virtual void MouseMoved(BPoint where, uint32 transit,
108 const BMessage* dragMessage)
110 if (dragMessage != NULL)
111 return;
113 if ((transit != B_INSIDE_VIEW && transit != B_ENTERED_VIEW)
114 || where.x > MinSize().width) {
115 SetRating(fPermanentRating);
116 return;
119 float hoverRating = _RatingForMousePos(where);
120 SetRating(hoverRating);
123 virtual void MouseDown(BPoint where)
125 SetPermanentRating(_RatingForMousePos(where));
126 BMessage message(MSG_PACKAGE_RATED);
127 message.AddFloat("rating", fPermanentRating);
128 Window()->PostMessage(&message, Window());
131 void SetPermanentRating(float rating)
133 fPermanentRating = rating;
134 SetRating(rating);
137 private:
138 float _RatingForMousePos(BPoint where)
140 return std::min(5.0f, ceilf(5.0f * where.x / MinSize().width));
143 float fPermanentRating;
147 static void
148 add_stabilities_to_menu(const StabilityRatingList& stabilities, BMenu* menu)
150 for (int i = 0; i < stabilities.CountItems(); i++) {
151 const StabilityRating& stability = stabilities.ItemAtFast(i);
152 BMessage* message = new BMessage(MSG_STABILITY_SELECTED);
153 message->AddString("name", stability.Name());
154 BMenuItem* item = new BMenuItem(stability.Label(), message);
155 menu->AddItem(item);
160 static void
161 add_languages_to_menu(const StringList& languages, BMenu* menu)
163 for (int i = 0; i < languages.CountItems(); i++) {
164 const BString& language = languages.ItemAtFast(i);
165 BMessage* message = new BMessage(MSG_LANGUAGE_SELECTED);
166 message->AddString("code", language);
167 BMenuItem* item = new BMenuItem(language, message);
168 menu->AddItem(item);
173 RatePackageWindow::RatePackageWindow(BWindow* parent, BRect frame,
174 Model& model)
176 BWindow(frame, B_TRANSLATE("Rate package"),
177 B_FLOATING_WINDOW_LOOK, B_FLOATING_SUBSET_WINDOW_FEEL,
178 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
179 fModel(model),
180 fRatingText(),
181 fTextEditor(new TextEditor(), true),
182 fRating(-1.0f),
183 fCommentLanguage(fModel.PreferredLanguage()),
184 fWorkerThread(-1)
186 AddToSubset(parent);
188 BStringView* ratingLabel = new BStringView("rating label",
189 B_TRANSLATE("Your rating:"));
191 fSetRatingView = new SetRatingView();
193 fTextView = new TextDocumentView();
194 ScrollView* textScrollView = new ScrollView(
195 "rating scroll view", fTextView);
197 // Get a TextDocument with default paragraph and character style
198 MarkupParser parser;
199 fRatingText = parser.CreateDocumentFromMarkup("");
201 fTextView->SetInsets(10.0f);
202 fTextView->SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
203 fTextView->SetTextDocument(fRatingText);
204 fTextView->SetTextEditor(fTextEditor);
206 // Construct stability rating popup
207 BPopUpMenu* stabilityMenu = new BPopUpMenu(B_TRANSLATE("Stability"));
208 fStabilityField = new BMenuField("stability",
209 B_TRANSLATE("Stability:"), stabilityMenu);
211 fStabilityCodes.Add(StabilityRating(
212 B_TRANSLATE("Not specified"), "unspecified"));
213 fStabilityCodes.Add(StabilityRating(
214 B_TRANSLATE("Stable"), "stable"));
215 fStabilityCodes.Add(StabilityRating(
216 B_TRANSLATE("Mostly stable"), "mostlystable"));
217 fStabilityCodes.Add(StabilityRating(
218 B_TRANSLATE("Unstable but usable"), "unstablebutusable"));
219 fStabilityCodes.Add(StabilityRating(
220 B_TRANSLATE("Very unstable"), "veryunstable"));
221 fStabilityCodes.Add(StabilityRating(
222 B_TRANSLATE("Does not start"), "nostart"));
224 add_stabilities_to_menu(fStabilityCodes, stabilityMenu);
225 stabilityMenu->SetTargetForItems(this);
227 fStability = fStabilityCodes.ItemAt(0).Name();
228 stabilityMenu->ItemAt(0)->SetMarked(true);
230 // Construct languages popup
231 BPopUpMenu* languagesMenu = new BPopUpMenu(B_TRANSLATE("Language"));
232 fCommentLanguageField = new BMenuField("language",
233 B_TRANSLATE("Comment language:"), languagesMenu);
235 add_languages_to_menu(fModel.SupportedLanguages(), languagesMenu);
236 languagesMenu->SetTargetForItems(this);
238 BMenuItem* defaultItem = languagesMenu->ItemAt(
239 fModel.SupportedLanguages().IndexOf(fCommentLanguage));
240 if (defaultItem != NULL)
241 defaultItem->SetMarked(true);
243 fRatingActiveCheckBox = new BCheckBox("rating active",
244 B_TRANSLATE("Other users can see this rating"),
245 new BMessage(MSG_RATING_ACTIVE_CHANGED));
246 // Hide the check mark by default, it will be made visible when
247 // the user already made a rating and it is loaded
248 fRatingActiveCheckBox->Hide();
250 // Construct buttons
251 fCancelButton = new BButton("cancel", B_TRANSLATE("Cancel"),
252 new BMessage(B_QUIT_REQUESTED));
254 fSendButton = new BButton("send", B_TRANSLATE("Send"),
255 new BMessage(MSG_SEND));
257 // Build layout
258 BLayoutBuilder::Group<>(this, B_VERTICAL)
259 .AddGrid()
260 .Add(ratingLabel, 0, 0)
261 .Add(fSetRatingView, 1, 0)
262 .AddMenuField(fStabilityField, 0, 1)
263 .AddMenuField(fCommentLanguageField, 0, 2)
264 .End()
265 .Add(textScrollView)
266 .AddGroup(B_HORIZONTAL)
267 .Add(fRatingActiveCheckBox)
268 .AddGlue()
269 .Add(fCancelButton)
270 .Add(fSendButton)
271 .End()
272 .SetInsets(B_USE_WINDOW_INSETS)
275 // NOTE: Do not make Send the default button. The user might want
276 // to type line-breaks instead of sending when hitting RETURN.
278 CenterIn(parent->Frame());
282 RatePackageWindow::~RatePackageWindow()
287 void
288 RatePackageWindow::MessageReceived(BMessage* message)
290 switch (message->what) {
291 case MSG_PACKAGE_RATED:
292 message->FindFloat("rating", &fRating);
293 break;
295 case MSG_STABILITY_SELECTED:
296 message->FindString("name", &fStability);
297 break;
299 case MSG_LANGUAGE_SELECTED:
300 message->FindString("code", &fCommentLanguage);
301 break;
303 case MSG_RATING_ACTIVE_CHANGED:
305 int32 value;
306 if (message->FindInt32("be:value", &value) == B_OK)
307 fRatingActive = value == B_CONTROL_ON;
308 break;
311 case MSG_SEND:
312 _SendRating();
313 break;
315 default:
316 BWindow::MessageReceived(message);
317 break;
322 void
323 RatePackageWindow::SetPackage(const PackageInfoRef& package)
325 BAutolock locker(this);
326 if (!locker.IsLocked() || fWorkerThread >= 0)
327 return;
329 fPackage = package;
331 BString windowTitle(B_TRANSLATE("Rate %Package%"));
332 windowTitle.ReplaceAll("%Package%", package->Title());
333 SetTitle(windowTitle);
335 // See if the user already made a rating for this package,
336 // pre-fill the UI with that rating. (When sending the rating, the
337 // old one will be replaced.)
338 thread_id thread = spawn_thread(&_QueryRatingThreadEntry,
339 "Query rating", B_NORMAL_PRIORITY, this);
340 if (thread >= 0)
341 _SetWorkerThread(thread);
345 void
346 RatePackageWindow::_SendRating()
348 thread_id thread = spawn_thread(&_SendRatingThreadEntry,
349 "Send rating", B_NORMAL_PRIORITY, this);
350 if (thread >= 0)
351 _SetWorkerThread(thread);
355 void
356 RatePackageWindow::_SetWorkerThread(thread_id thread)
358 if (!Lock())
359 return;
361 bool enabled = thread < 0;
363 // fTextEditor->SetEnabled(enabled);
364 // fSetRatingView->SetEnabled(enabled);
365 fStabilityField->SetEnabled(enabled);
366 fCommentLanguageField->SetEnabled(enabled);
367 fSendButton->SetEnabled(enabled);
369 if (thread >= 0) {
370 fWorkerThread = thread;
371 resume_thread(fWorkerThread);
372 } else {
373 fWorkerThread = -1;
376 Unlock();
380 int32
381 RatePackageWindow::_QueryRatingThreadEntry(void* data)
383 RatePackageWindow* window = reinterpret_cast<RatePackageWindow*>(data);
384 window->_QueryRatingThread();
385 return 0;
389 void
390 RatePackageWindow::_QueryRatingThread()
392 if (!Lock()) {
393 fprintf(stderr, "rating query: Failed to lock window\n");
394 return;
397 PackageInfoRef package(fPackage);
399 Unlock();
401 BAutolock locker(fModel.Lock());
402 BString username = fModel.Username();
403 locker.Unlock();
405 if (package.Get() == NULL) {
406 fprintf(stderr, "rating query: No package\n");
407 _SetWorkerThread(-1);
408 return;
411 WebAppInterface interface;
412 BMessage info;
413 const DepotInfo* depot = fModel.DepotForName(package->DepotName());
414 BString repositoryCode;
416 if (depot != NULL)
417 repositoryCode = depot->WebAppRepositoryCode();
419 if (repositoryCode.Length() == 0) {
420 printf("unable to obtain the repository code for depot; %s\n",
421 package->DepotName().String());
422 } else {
423 status_t status = interface.RetrieveUserRating(
424 package->Name(), package->Version(), package->Architecture(),
425 repositoryCode, username, info);
427 // info.PrintToStream();
429 BMessage result;
430 if (status == B_OK && info.FindMessage("result", &result) == B_OK
431 && Lock()) {
433 result.FindString("code", &fRatingID);
434 result.FindBool("active", &fRatingActive);
435 BString comment;
436 if (result.FindString("comment", &comment) == B_OK) {
437 MarkupParser parser;
438 fRatingText = parser.CreateDocumentFromMarkup(comment);
439 fTextView->SetTextDocument(fRatingText);
441 if (result.FindString("userRatingStabilityCode",
442 &fStability) == B_OK) {
443 int32 index = 0;
444 for (int32 i = fStabilityCodes.CountItems() - 1; i >= 0; i--) {
445 const StabilityRating& stability
446 = fStabilityCodes.ItemAtFast(i);
447 if (stability.Name() == fStability) {
448 index = i;
449 break;
452 BMenuItem* item = fStabilityField->Menu()->ItemAt(index);
453 if (item != NULL)
454 item->SetMarked(true);
456 if (result.FindString("naturalLanguageCode",
457 &fCommentLanguage) == B_OK) {
458 BMenuItem* item = fCommentLanguageField->Menu()->ItemAt(
459 fModel.SupportedLanguages().IndexOf(fCommentLanguage));
460 if (item != NULL)
461 item->SetMarked(true);
463 double rating;
464 if (result.FindDouble("rating", &rating) == B_OK) {
465 fRating = (float)rating;
466 fSetRatingView->SetPermanentRating(fRating);
469 fRatingActiveCheckBox->SetValue(fRatingActive);
470 fRatingActiveCheckBox->Show();
472 fSendButton->SetLabel(B_TRANSLATE("Update"));
474 Unlock();
475 } else {
476 fprintf(stderr, "rating query: Failed response: %s\n",
477 strerror(status));
478 if (!info.IsEmpty())
479 info.PrintToStream();
483 _SetWorkerThread(-1);
487 int32
488 RatePackageWindow::_SendRatingThreadEntry(void* data)
490 RatePackageWindow* window = reinterpret_cast<RatePackageWindow*>(data);
491 window->_SendRatingThread();
492 return 0;
496 void
497 RatePackageWindow::_SendRatingThread()
499 if (!Lock()) {
500 fprintf(stderr, "upload rating: Failed to lock window\n");
501 return;
504 BString package = fPackage->Name();
505 BString architecture = fPackage->Architecture();
506 BString repositoryCode;
507 int rating = (int)fRating;
508 BString stability = fStability;
509 BString comment = fRatingText->Text();
510 BString languageCode = fCommentLanguage;
511 BString ratingID = fRatingID;
512 bool active = fRatingActive;
514 const DepotInfo* depot = fModel.DepotForName(fPackage->DepotName());
516 if (depot != NULL)
517 repositoryCode = depot->WebAppRepositoryCode();
519 WebAppInterface interface = fModel.GetWebAppInterface();
521 Unlock();
523 if (repositoryCode.Length() == 0) {
524 printf("unable to find the web app repository code for the local "
525 "depot %s\n",
526 fPackage->DepotName().String());
527 return;
530 if (stability == "unspecified")
531 stability = "";
533 status_t status;
534 BMessage info;
535 if (ratingID.Length() > 0) {
536 status = interface.UpdateUserRating(ratingID,
537 languageCode, comment, stability, rating, active, info);
538 } else {
539 status = interface.CreateUserRating(package, architecture,
540 repositoryCode, languageCode, comment, stability, rating, info);
543 BString error = B_TRANSLATE(
544 "There was a puzzling response from the web service.");
546 BMessage result;
547 if (status == B_OK) {
548 if (info.FindMessage("result", &result) == B_OK) {
549 error = "";
550 } else if (info.FindMessage("error", &result) == B_OK) {
551 result.PrintToStream();
552 BString message;
553 if (result.FindString("message", &message) == B_OK) {
554 if (message == "objectnotfound") {
555 error = B_TRANSLATE("The package was not found by the "
556 "web service. This probably means that it comes "
557 "from a depot which is not tracked there. Rating "
558 "such packages is unfortunately not supported.");
559 } else {
560 error << B_TRANSLATE(" It responded with: ");
561 error << message;
565 } else {
566 error = B_TRANSLATE(
567 "It was not possible to contact the web service.");
570 if (!error.IsEmpty()) {
571 BString failedTitle;
572 if (ratingID.Length() > 0)
573 failedTitle = B_TRANSLATE("Failed to update rating");
574 else
575 failedTitle = B_TRANSLATE("Failed to rate package");
577 BAlert* alert = new(std::nothrow) BAlert(
578 failedTitle,
579 error,
580 B_TRANSLATE("Close"), NULL, NULL,
581 B_WIDTH_AS_USUAL, B_WARNING_ALERT);
583 if (alert != NULL)
584 alert->Go();
586 fprintf(stderr,
587 B_TRANSLATE("Failed to create or update rating: %s\n"),
588 error.String());
589 if (!info.IsEmpty())
590 info.PrintToStream();
592 _SetWorkerThread(-1);
593 } else {
594 _SetWorkerThread(-1);
596 fModel.PopulatePackage(fPackage,
597 Model::POPULATE_FORCE | Model::POPULATE_USER_RATINGS);
599 BMessenger(this).SendMessage(B_QUIT_REQUESTED);
601 BString message;
602 if (ratingID.Length() > 0) {
603 message = B_TRANSLATE("Your rating was updated successfully.");
604 } else {
605 message = B_TRANSLATE("Your rating was uploaded successfully. "
606 "You can update or remove it at any time by rating the "
607 "package again.");
610 BAlert* alert = new(std::nothrow) BAlert(
611 B_TRANSLATE("Success"),
612 message,
613 B_TRANSLATE("Close"));
615 if (alert != NULL)
616 alert->Go();