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.
7 #include "RatePackageWindow.h"
17 #include <LayoutBuilder.h>
18 #include <MenuField.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"
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
44 class ScrollView
: public BScrollView
{
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();
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();
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();
89 hScrollBar
->MoveTo(rect
.left
, rect
.top
);
90 hScrollBar
->ResizeTo(rect
.Width(), rect
.Height());
96 class SetRatingView
: public RatingView
{
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
)
113 if ((transit
!= B_INSIDE_VIEW
&& transit
!= B_ENTERED_VIEW
)
114 || where
.x
> MinSize().width
) {
115 SetRating(fPermanentRating
);
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
;
138 float _RatingForMousePos(BPoint where
)
140 return std::min(5.0f
, ceilf(5.0f
* where
.x
/ MinSize().width
));
143 float fPermanentRating
;
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
);
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
);
173 RatePackageWindow::RatePackageWindow(BWindow
* parent
, BRect frame
,
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
),
181 fTextEditor(new TextEditor(), true),
183 fCommentLanguage(fModel
.PreferredLanguage()),
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
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();
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
));
258 BLayoutBuilder::Group
<>(this, B_VERTICAL
)
260 .Add(ratingLabel
, 0, 0)
261 .Add(fSetRatingView
, 1, 0)
262 .AddMenuField(fStabilityField
, 0, 1)
263 .AddMenuField(fCommentLanguageField
, 0, 2)
266 .AddGroup(B_HORIZONTAL
)
267 .Add(fRatingActiveCheckBox
)
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()
288 RatePackageWindow::MessageReceived(BMessage
* message
)
290 switch (message
->what
) {
291 case MSG_PACKAGE_RATED
:
292 message
->FindFloat("rating", &fRating
);
295 case MSG_STABILITY_SELECTED
:
296 message
->FindString("name", &fStability
);
299 case MSG_LANGUAGE_SELECTED
:
300 message
->FindString("code", &fCommentLanguage
);
303 case MSG_RATING_ACTIVE_CHANGED
:
306 if (message
->FindInt32("be:value", &value
) == B_OK
)
307 fRatingActive
= value
== B_CONTROL_ON
;
316 BWindow::MessageReceived(message
);
323 RatePackageWindow::SetPackage(const PackageInfoRef
& package
)
325 BAutolock
locker(this);
326 if (!locker
.IsLocked() || fWorkerThread
>= 0)
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);
341 _SetWorkerThread(thread
);
346 RatePackageWindow::_SendRating()
348 thread_id thread
= spawn_thread(&_SendRatingThreadEntry
,
349 "Send rating", B_NORMAL_PRIORITY
, this);
351 _SetWorkerThread(thread
);
356 RatePackageWindow::_SetWorkerThread(thread_id thread
)
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
);
370 fWorkerThread
= thread
;
371 resume_thread(fWorkerThread
);
381 RatePackageWindow::_QueryRatingThreadEntry(void* data
)
383 RatePackageWindow
* window
= reinterpret_cast<RatePackageWindow
*>(data
);
384 window
->_QueryRatingThread();
390 RatePackageWindow::_QueryRatingThread()
393 fprintf(stderr
, "rating query: Failed to lock window\n");
397 PackageInfoRef
package(fPackage
);
401 BAutolock
locker(fModel
.Lock());
402 BString username
= fModel
.Username();
405 if (package
.Get() == NULL
) {
406 fprintf(stderr
, "rating query: No package\n");
407 _SetWorkerThread(-1);
411 WebAppInterface interface
;
413 const DepotInfo
* depot
= fModel
.DepotForName(package
->DepotName());
414 BString repositoryCode
;
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());
423 status_t status
= interface
.RetrieveUserRating(
424 package
->Name(), package
->Version(), package
->Architecture(),
425 repositoryCode
, username
, info
);
427 // info.PrintToStream();
430 if (status
== B_OK
&& info
.FindMessage("result", &result
) == B_OK
433 result
.FindString("code", &fRatingID
);
434 result
.FindBool("active", &fRatingActive
);
436 if (result
.FindString("comment", &comment
) == B_OK
) {
438 fRatingText
= parser
.CreateDocumentFromMarkup(comment
);
439 fTextView
->SetTextDocument(fRatingText
);
441 if (result
.FindString("userRatingStabilityCode",
442 &fStability
) == B_OK
) {
444 for (int32 i
= fStabilityCodes
.CountItems() - 1; i
>= 0; i
--) {
445 const StabilityRating
& stability
446 = fStabilityCodes
.ItemAtFast(i
);
447 if (stability
.Name() == fStability
) {
452 BMenuItem
* item
= fStabilityField
->Menu()->ItemAt(index
);
454 item
->SetMarked(true);
456 if (result
.FindString("naturalLanguageCode",
457 &fCommentLanguage
) == B_OK
) {
458 BMenuItem
* item
= fCommentLanguageField
->Menu()->ItemAt(
459 fModel
.SupportedLanguages().IndexOf(fCommentLanguage
));
461 item
->SetMarked(true);
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"));
476 fprintf(stderr
, "rating query: Failed response: %s\n",
479 info
.PrintToStream();
483 _SetWorkerThread(-1);
488 RatePackageWindow::_SendRatingThreadEntry(void* data
)
490 RatePackageWindow
* window
= reinterpret_cast<RatePackageWindow
*>(data
);
491 window
->_SendRatingThread();
497 RatePackageWindow::_SendRatingThread()
500 fprintf(stderr
, "upload rating: Failed to lock window\n");
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());
517 repositoryCode
= depot
->WebAppRepositoryCode();
519 WebAppInterface interface
= fModel
.GetWebAppInterface();
523 if (repositoryCode
.Length() == 0) {
524 printf("unable to find the web app repository code for the local "
526 fPackage
->DepotName().String());
530 if (stability
== "unspecified")
535 if (ratingID
.Length() > 0) {
536 status
= interface
.UpdateUserRating(ratingID
,
537 languageCode
, comment
, stability
, rating
, active
, info
);
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.");
547 if (status
== B_OK
) {
548 if (info
.FindMessage("result", &result
) == B_OK
) {
550 } else if (info
.FindMessage("error", &result
) == B_OK
) {
551 result
.PrintToStream();
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.");
560 error
<< B_TRANSLATE(" It responded with: ");
567 "It was not possible to contact the web service.");
570 if (!error
.IsEmpty()) {
572 if (ratingID
.Length() > 0)
573 failedTitle
= B_TRANSLATE("Failed to update rating");
575 failedTitle
= B_TRANSLATE("Failed to rate package");
577 BAlert
* alert
= new(std::nothrow
) BAlert(
580 B_TRANSLATE("Close"), NULL
, NULL
,
581 B_WIDTH_AS_USUAL
, B_WARNING_ALERT
);
587 B_TRANSLATE("Failed to create or update rating: %s\n"),
590 info
.PrintToStream();
592 _SetWorkerThread(-1);
594 _SetWorkerThread(-1);
596 fModel
.PopulatePackage(fPackage
,
597 Model::POPULATE_FORCE
| Model::POPULATE_USER_RATINGS
);
599 BMessenger(this).SendMessage(B_QUIT_REQUESTED
);
602 if (ratingID
.Length() > 0) {
603 message
= B_TRANSLATE("Your rating was updated successfully.");
605 message
= B_TRANSLATE("Your rating was uploaded successfully. "
606 "You can update or remove it at any time by rating the "
610 BAlert
* alert
= new(std::nothrow
) BAlert(
611 B_TRANSLATE("Success"),
613 B_TRANSLATE("Close"));