32
32
from stars import Star
33
33
from softwarecenter.utils import (
34
34
get_person_from_config,
36
upstream_version_compare,
36
upstream_version_compare,
42
from softwarecenter.i18n import get_languages, langcode_to_name
42
from softwarecenter.i18n import (
44
from softwarecenter.netstatus import network_state_is_connected, get_network_watcher
47
from softwarecenter.netstatus import (
48
network_state_is_connected,
45
51
from softwarecenter.enums import (
50
56
from softwarecenter.backend.reviews import UsefulnessCache
52
58
from softwarecenter.ui.gtk3.em import StockEms
59
65
COL_LANGCODE) = range(2)
61
68
class UIReviewsList(Gtk.VBox):
64
'new-review':(GObject.SignalFlags.RUN_FIRST,
71
'new-review': (GObject.SignalFlags.RUN_FIRST,
67
'report-abuse':(GObject.SignalFlags.RUN_FIRST,
74
'report-abuse': (GObject.SignalFlags.RUN_FIRST,
69
76
(GObject.TYPE_PYOBJECT,)),
70
'submit-usefulness':(GObject.SignalFlags.RUN_FIRST,
77
'submit-usefulness': (GObject.SignalFlags.RUN_FIRST,
72
79
(GObject.TYPE_PYOBJECT, bool)),
73
'modify-review':(GObject.SignalFlags.RUN_FIRST,
75
(GObject.TYPE_PYOBJECT,)),
76
'delete-review':(GObject.SignalFlags.RUN_FIRST,
78
(GObject.TYPE_PYOBJECT,)),
79
'more-reviews-clicked':(GObject.SignalFlags.RUN_FIRST,
80
'modify-review': (GObject.SignalFlags.RUN_FIRST,
82
(GObject.TYPE_PYOBJECT,)),
83
'delete-review': (GObject.SignalFlags.RUN_FIRST,
85
(GObject.TYPE_PYOBJECT,)),
86
'more-reviews-clicked': (GObject.SignalFlags.RUN_FIRST,
82
'different-review-language-clicked':(GObject.SignalFlags.RUN_FIRST,
89
'different-review-language-clicked': (GObject.SignalFlags.RUN_FIRST,
84
(GObject.TYPE_STRING,) ),
85
'review-sort-changed':(GObject.SignalFlags.RUN_FIRST,
91
(GObject.TYPE_STRING,)),
92
'review-sort-changed': (GObject.SignalFlags.RUN_FIRST,
87
(GObject.TYPE_INT,) ),
90
97
def __init__(self, parent):
136
143
self.review_language.add_attribute(cell, "text", COL_LANGNAME)
137
144
self.review_language_model = Gtk.ListStore(str, str)
138
145
for lang in get_languages():
139
self.review_language_model.append( (langcode_to_name(lang), lang) )
140
self.review_language_model.append( (_('Any language'), 'any') )
146
self.review_language_model.append((langcode_to_name(lang), lang))
147
self.review_language_model.append((_('Any language'), 'any'))
141
148
self.review_language.set_model(self.review_language_model)
142
149
self.review_language.set_active(0)
143
150
self.review_language.connect(
192
198
for r in self.reviews:
193
199
pkgversion = self._parent.app_details.version
194
review = UIReview(r, pkgversion, self.logged_in_person, self.useful_votes)
200
review = UIReview(r, pkgversion, self.logged_in_person,
195
202
self.vbox.pack_start(review, True, True, 0)
198
204
def _be_the_first_to_review(self):
199
205
s = _('Be the first to review it')
200
206
self.new_review.set_label(s)
201
207
self.vbox.pack_start(NoReviewYetWriteOne(), True, True, 0)
202
208
self.vbox.show_all()
205
210
def _install_to_review(self):
206
s = '<small>%s</small>' % _("You need to install this before you can review it")
211
s = ('<small>%s</small>' %
212
_("You need to install this before you can review it"))
207
213
self.install_first_label = Gtk.Label(label=s)
208
214
self.install_first_label.set_use_markup(True)
209
215
self.install_first_label.set_alignment(1.0, 0.5)
210
216
self.header.pack_start(self.install_first_label, False, False, 0)
211
217
self.install_first_label.show()
214
219
# FIXME: this needs to be smarter in the future as we will
215
220
# not allow multiple reviews for the same software version
216
221
def _any_reviews_current_user(self):
225
230
m = EmbeddedMessage(title, msg, 'network-offline')
226
231
self.vbox.pack_start(m, True, True, 0)
229
234
def _clear_vbox(self, vbox):
230
235
children = vbox.get_children()
231
236
for child in children:
234
# FIXME: instead of clear/add_reviews/configure_reviews_ui we should provide
235
# a single show_reviews(reviews_data_list)
239
# FIXME: instead of clear/add_reviews/configure_reviews_ui we should
240
# provide a single show_reviews(reviews_data_list)
236
241
def configure_reviews_ui(self):
237
242
""" this needs to be called after add_reviews, it will actually
381
382
self.vbox.pack_start(a, False, False, 0)
385
385
def hide_spinner(self):
386
386
for child in self.vbox.get_children():
387
387
if isinstance(child, Gtk.Alignment):
391
390
def draw(self, cr, a):
392
391
for r in self.vbox:
393
392
if isinstance(r, (UIReview)):
394
393
r.draw(cr, r.get_allocation())
398
396
class UIReview(Gtk.VBox):
399
397
""" the UI for a individual review including all button to mark
400
398
useful/inappropriate etc
402
def __init__(self, review_data=None, app_version=None,
400
def __init__(self, review_data=None, app_version=None,
403
401
logged_in_person=None, useful_votes=None):
404
402
GObject.GObject.__init__(self)
405
403
self.set_spacing(StockEms.SMALL)
420
418
self.delete_error_img = Gtk.Image()
421
419
self.delete_error_img.set_from_stock(
422
420
Gtk.STOCK_DIALOG_ERROR,
423
Gtk.IconSize.SMALL_TOOLBAR)
421
Gtk.IconSize.SMALL_TOOLBAR)
424
422
self.submit_error_img = Gtk.Image()
425
423
self.submit_error_img.set_from_stock(
426
424
Gtk.STOCK_DIALOG_ERROR,
427
425
Gtk.IconSize.SMALL_TOOLBAR)
428
426
self.submit_status_spinner = Gtk.Spinner()
429
self.submit_status_spinner.set_size_request(12,12)
427
self.submit_status_spinner.set_size_request(12, 12)
430
428
self.delete_status_spinner = Gtk.Spinner()
431
self.delete_status_spinner.set_size_request(12,12)
429
self.delete_status_spinner.set_size_request(12, 12)
432
430
self.acknowledge_error = Gtk.Button()
433
431
label = Gtk.Label()
434
432
label.set_markup('<small>%s</small>' % _("OK"))
476
473
reviews = self.get_ancestor(UIReviewsList)
478
475
reviews.emit("modify-review", self.id)
480
477
def _on_useful_clicked(self, btn, is_useful):
481
478
reviews = self.get_ancestor(UIReviewsList)
483
480
self._usefulness_ui_update('progress')
484
481
reviews.emit("submit-usefulness", self.id, is_useful)
486
def _on_error_acknowledged(self, button, current_user_reviewer, useful_total, useful_favorable):
483
def _on_error_acknowledged(self, button, current_user_reviewer,
484
useful_total, useful_favorable):
487
485
self.usefulness_error = False
488
self._usefulness_ui_update('renew', current_user_reviewer, useful_total, useful_favorable)
490
def _usefulness_ui_update(self, type, current_user_reviewer=False, useful_total=0, useful_favorable=0):
486
self._usefulness_ui_update('renew', current_user_reviewer,
487
useful_total, useful_favorable)
489
def _usefulness_ui_update(self, type, current_user_reviewer=False,
490
useful_total=0, useful_favorable=0):
491
491
self._hide_usefulness_elements()
492
492
#print "_usefulness_ui_update: %s" % type
493
493
if type == 'renew':
494
self._build_usefulness_ui(current_user_reviewer, useful_total, useful_favorable, self.useful_votes)
494
self._build_usefulness_ui(current_user_reviewer, useful_total,
495
useful_favorable, self.useful_votes)
496
497
if type == 'progress':
497
self.status_label = Gtk.Label.new("<small>%s</small>" % _(u"Submitting now\u2026"))
498
self.status_label = Gtk.Label.new(
499
"<small>%s</small>" % _(u"Submitting now\u2026"))
498
500
self.status_label.set_use_markup(True)
499
self.status_box.pack_start(self.submit_status_spinner, False, False, 0)
501
self.status_box.pack_start(self.submit_status_spinner, False,
500
503
self.submit_status_spinner.show()
501
504
self.submit_status_spinner.start()
502
self.status_label.set_padding(2,0)
505
self.status_label.set_padding(2, 0)
503
506
self.status_box.pack_start(self.status_label, False, False, 0)
504
507
self.status_label.show()
505
508
if type == 'error':
506
509
self.submit_error_img.show()
507
self.status_label = Gtk.Label.new("<small>%s</small>" % _("Error submitting usefulness"))
510
self.status_label = Gtk.Label.new(
511
"<small>%s</small>" % _("Error submitting usefulness"))
508
512
self.status_label.set_use_markup(True)
509
513
self.status_box.pack_start(self.submit_error_img, False, False, 0)
510
self.status_label.set_padding(2,0)
514
self.status_label.set_padding(2, 0)
511
515
self.status_box.pack_start(self.status_label, False, False, 0)
512
516
self.status_label.show()
513
517
self.acknowledge_error.show()
514
518
self.status_box.pack_start(self.acknowledge_error, False, False, 0)
515
self.acknowledge_error.connect('clicked', self._on_error_acknowledged, current_user_reviewer, useful_total, useful_favorable)
519
self.acknowledge_error.connect('clicked',
520
self._on_error_acknowledged, current_user_reviewer,
521
useful_total, useful_favorable)
516
522
self.status_box.show()
517
523
self.footer.pack_start(self.status_box, False, False, 0)
520
525
def _hide_usefulness_elements(self):
521
526
""" hide all usefulness elements """
526
531
widget = getattr(self, attr, None)
531
535
def _get_datetime_from_review_date(self, raw_date_str):
532
536
# example raw_date str format: 2011-01-28 19:15:21
533
537
return datetime.datetime.strptime(raw_date_str, '%Y-%m-%d %H:%M:%S')
535
def _delete_ui_update(self, type, current_user_reviewer=False, action=None):
539
def _delete_ui_update(self, type, current_user_reviewer=False,
536
541
self._hide_delete_elements()
537
542
if type == 'renew':
538
543
self._build_delete_flag_ui(current_user_reviewer)
540
545
if type == 'progress':
541
546
self.delete_status_spinner.start()
542
547
self.delete_status_spinner.show()
543
self.delete_status_label = Gtk.Label("<small><b>%s</b></small>" % _(u"Deleting now\u2026"))
544
self.delete_status_box.pack_start(self.delete_status_spinner, False, False, 0)
548
self.delete_status_label = Gtk.Label(
549
"<small><b>%s</b></small>" % _(u"Deleting now\u2026"))
550
self.delete_status_box.pack_start(self.delete_status_spinner,
545
552
self.delete_status_label.set_use_markup(True)
546
self.delete_status_label.set_padding(2,0)
547
self.delete_status_box.pack_start(self.delete_status_label, False, False, 0)
553
self.delete_status_label.set_padding(2, 0)
554
self.delete_status_box.pack_start(self.delete_status_label, False,
548
556
self.delete_status_label.show()
549
557
if type == 'error':
550
558
self.delete_error_img.show()
551
# build full strings for easier i18n
559
# build full strings for easier i18n
552
560
if action == 'deleting':
553
561
s = _("Error deleting review")
554
562
elif action == 'modifying':
555
563
s = _("Error modifying review")
557
# or unknown error, but we are in string freeze,
565
# or unknown error, but we are in string freeze,
558
566
# should never happen anyway
559
567
s = _("Internal Error")
560
self.delete_status_label = Gtk.Label("<small><b>%s</b></small>" % s)
561
self.delete_status_box.pack_start(self.delete_error_img, False, False, 0)
568
self.delete_status_label = Gtk.Label(
569
"<small><b>%s</b></small>" % s)
570
self.delete_status_box.pack_start(self.delete_error_img,
562
572
self.delete_status_label.set_use_markup(True)
563
self.delete_status_label.set_padding(2,0)
564
self.delete_status_box.pack_start(self.delete_status_label, False, False, 0)
573
self.delete_status_label.set_padding(2, 0)
574
self.delete_status_box.pack_start(self.delete_status_label,
565
576
self.delete_status_label.show()
566
577
self.delete_acknowledge_error.show()
567
self.delete_status_box.pack_start(self.delete_acknowledge_error, False, False, 0)
568
self.delete_acknowledge_error.connect('clicked', self._on_delete_error_acknowledged, current_user_reviewer)
578
self.delete_status_box.pack_start(self.delete_acknowledge_error,
580
self.delete_acknowledge_error.connect('clicked',
581
self._on_delete_error_acknowledged, current_user_reviewer)
569
582
self.delete_status_box.show()
570
583
self.footer.pack_end(self.delete_status_box, False, False, 0)
573
585
def _on_delete_clicked(self, btn):
574
586
reviews = self.get_ancestor(UIReviewsList)
583
595
def _hide_delete_elements(self):
584
596
""" hide all delete elements """
585
597
for attr in ["complain", "edit", "delete", "delete_status_spinner",
586
"delete_error_img", "delete_status_box", "delete_status_label",
587
"delete_acknowledge_error", "flagbox"
598
"delete_error_img", "delete_status_box",
599
"delete_status_label", "delete_acknowledge_error",
589
602
o = getattr(self, attr, None)
594
606
def _build(self, review_data, app_version, logged_in_person, useful_votes):
595
# all the attributes of review_data may need markup escape,
607
# all the attributes of review_data may need markup escape,
596
608
# depening on if they are used as text or markup
597
609
self.id = review_data.id
598
610
self.person = review_data.reviewer_username
663
675
current_user_reviewer = True
665
677
self._build_usefulness_ui(current_user_reviewer, useful_total,
666
useful_favorable, useful_votes, useful_submit_error)
678
useful_favorable, useful_votes,
668
681
self.flagbox = Gtk.HBox()
669
682
self.flagbox.set_spacing(4)
670
self._build_delete_flag_ui(current_user_reviewer, delete_error, modify_error)
683
self._build_delete_flag_ui(current_user_reviewer, delete_error,
671
685
self.footer.pack_end(self.flagbox, False, False, 0)
673
687
# connect network signals
674
688
self.connect("realize", lambda w: self._on_network_state_change())
675
689
watcher = get_network_watcher()
677
"changed", lambda w,s: self._on_network_state_change())
680
def _build_usefulness_ui(self, current_user_reviewer, useful_total,
681
useful_favorable, useful_votes, usefulness_submit_error=False):
691
"changed", lambda w, s: self._on_network_state_change())
693
def _build_usefulness_ui(self, current_user_reviewer, useful_total,
694
useful_favorable, useful_votes,
695
usefulness_submit_error=False):
682
696
if usefulness_submit_error:
683
self._usefulness_ui_update('error', current_user_reviewer,
697
self._usefulness_ui_update('error', current_user_reviewer,
684
698
useful_total, useful_favorable)
686
700
already_voted = useful_votes.check_for_usefulness(self.id)
687
#get correct label based on retrieved usefulness totals and
701
#get correct label based on retrieved usefulness totals and
688
702
# if user is reviewer
689
703
self.useful = self._get_usefulness_label(
690
current_user_reviewer, useful_total, useful_favorable, already_voted)
704
current_user_reviewer, useful_total, useful_favorable,
691
706
self.useful.set_use_markup(True)
692
707
#vertically centre so it lines up with the Yes and No buttons
693
708
self.useful.set_alignment(0, 0.5)
787
804
"found this review helpful; you did not.",
788
805
"%(useful_favorable)s of %(useful_total)s people "
789
806
"found this review helpful; you did not.",
790
useful_total) % { 'useful_total' : useful_total,
791
'useful_favorable' : useful_favorable,
808
'useful_total': useful_total,
809
'useful_favorable': useful_favorable,
794
812
m = '<small>%s</small>'
795
813
label = Gtk.Label()
796
814
label.set_name("subtle-label")
797
815
label.set_markup(m % s)
800
def _build_delete_flag_ui(self, current_user_reviewer, delete_error=False, modify_error=False):
818
def _build_delete_flag_ui(self, current_user_reviewer, delete_error=False,
802
821
self._delete_ui_update('error', current_user_reviewer, 'deleting')
803
822
elif modify_error:
814
833
self.edit.connect('clicked', self._on_modify_clicked)
815
834
self.delete.connect('clicked', self._on_delete_clicked)
817
# Translators: This link is for flagging a review as inappropriate.
818
# To minimize repetition, if at all possible, keep it to a single word.
819
# If your language has an obvious verb, it won't need a question mark.
836
# Translators: This link is for flagging a review as
837
# inappropriate. To minimize repetition, if at all possible,
838
# keep it to a single word. If your language has an obvious
839
# verb, it won't need a question mark.
820
840
self.complain = Link(m % _('Inappropriate?'))
821
841
self.complain.set_name("subtle-label")
822
842
self.complain.set_sensitive(network_state_is_connected())
823
843
self.flagbox.pack_start(self.complain, False, False, 0)
824
844
self.complain.connect('clicked', self._on_report_abuse_clicked)
825
845
self.flagbox.show_all()
828
847
def _whom_when_markup(self, person, displayname, cur_t):
829
848
nice_date = get_nice_date_string(cur_t)