#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (C) 2010 Canonical
#
# Authors:
# Matthew McGowan
# Michael Vogt
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GObject, Pango, GLib
import datetime
import logging
import gettext
from gettext import gettext as _
from stars import Star
from softwarecenter.utils import (
get_person_from_config,
get_nice_date_string,
upstream_version_compare,
upstream_version,
utf8,
)
from softwarecenter.i18n import (
get_languages,
langcode_to_name,
)
from softwarecenter.netstatus import (
network_state_is_connected,
get_network_watcher,
)
from softwarecenter.enums import (
PkgStates,
ReviewSortMethods,
)
from softwarecenter.backend.reviews import UsefulnessCache
from softwarecenter.ui.gtk3.em import StockEms
from softwarecenter.ui.gtk3.widgets.buttons import Link
LOG_ALLOCATION = logging.getLogger("softwarecenter.ui.Gtk.get_allocation()")
LOG = logging.getLogger(__name__)
(COL_LANGNAME,
COL_LANGCODE) = range(2)
class UIReviewsList(Gtk.VBox):
__gsignals__ = {
'new-review': (GObject.SignalFlags.RUN_FIRST,
None,
()),
'report-abuse': (GObject.SignalFlags.RUN_FIRST,
None,
(GObject.TYPE_PYOBJECT,)),
'submit-usefulness': (GObject.SignalFlags.RUN_FIRST,
None,
(GObject.TYPE_PYOBJECT, bool)),
'modify-review': (GObject.SignalFlags.RUN_FIRST,
None,
(GObject.TYPE_PYOBJECT,)),
'delete-review': (GObject.SignalFlags.RUN_FIRST,
None,
(GObject.TYPE_PYOBJECT,)),
'more-reviews-clicked': (GObject.SignalFlags.RUN_FIRST,
None,
()),
'different-review-language-clicked': (GObject.SignalFlags.RUN_FIRST,
None,
(GObject.TYPE_STRING,)),
'review-sort-changed': (GObject.SignalFlags.RUN_FIRST,
None,
(GObject.TYPE_INT,)),
}
def __init__(self, parent):
Gtk.VBox.__init__(self)
self.set_spacing(12)
self.logged_in_person = get_person_from_config()
self._parent = parent
# this is a list of review data (softwarecenter.backend.reviews.Review)
self.reviews = []
# global review stats, this includes ratings in different languages
self.global_review_stats = None
# usefulness stuff
self.useful_votes = UsefulnessCache()
self.logged_in_person = None
# add header label
label = Gtk.Label()
label.set_markup('%s' % _("Reviews"))
label.set_padding(6, 6)
label.set_use_markup(True)
label.set_alignment(0, 0.5)
self.header = Gtk.HBox()
self.header.pack_start(label, False, False, 0)
# header
self.header.set_spacing(StockEms.MEDIUM)
# review sort method
self.sort_combo = Gtk.ComboBoxText()
self._current_sort = 0
for sort_method in ReviewSortMethods.REVIEW_SORT_LIST_ENTRIES:
self.sort_combo.append_text(_(sort_method))
self.sort_combo.set_active(self._current_sort)
self.sort_combo.connect('changed', self._on_sort_method_changed)
self.header.pack_end(self.sort_combo, False, False, 3)
# change language
self.review_language = Gtk.ComboBox()
cell = Gtk.CellRendererText()
self.review_language.pack_start(cell, True)
self.review_language.add_attribute(cell, "text", COL_LANGNAME)
self.review_language_model = Gtk.ListStore(str, str)
for lang in get_languages():
self.review_language_model.append((langcode_to_name(lang), lang))
self.review_language_model.append((_('Any language'), 'any'))
self.review_language.set_model(self.review_language_model)
self.review_language.set_active(0)
self.review_language.connect(
"changed", self._on_different_review_language_clicked)
self.header.pack_end(self.review_language, False, True, 0)
self.pack_start(self.header, False, False, 0)
self.reviews_info_hbox = Gtk.HBox()
self.new_review = Link(_('Write your own review'))
self.new_review.connect('clicked', lambda w: self.emit('new-review'))
self.reviews_info_hbox.pack_start(
self.new_review, False, False, StockEms.SMALL)
self.pack_start(self.reviews_info_hbox, True, True, 0)
# this is where the reviews end up
self.vbox = Gtk.VBox()
self.vbox.set_spacing(24)
self.pack_end(self.vbox, True, True, 0)
# ensure network state updates
self.no_network_msg = None
watcher = get_network_watcher()
watcher.connect(
"changed", lambda w, s: self._on_network_state_change())
self.show_all()
def _on_network_state_change(self):
is_connected = network_state_is_connected()
if is_connected:
self.new_review.set_sensitive(True)
if self.no_network_msg:
self.no_network_msg.hide()
else:
self.new_review.set_sensitive(False)
if self.no_network_msg:
self.no_network_msg.show()
def _on_button_new_clicked(self, button):
self.emit("new-review")
def _on_sort_method_changed(self, cb):
selection = self.sort_combo.get_active()
if selection == self._current_sort:
return
else:
self._current_sort = selection
self.emit("review-sort-changed", selection)
def update_useful_votes(self, my_votes):
self.useful_votes = my_votes
def _fill(self):
""" take the review data object from self.reviews and build the
UI vbox out of them
"""
self.logged_in_person = get_person_from_config()
is_first_for_version = None
if self.reviews:
previous_review = None
for r in self.reviews:
pkgversion = self._parent.app_details.version
if previous_review:
is_first_for_version = previous_review.version != r.version
else:
is_first_for_version = True
previous_review = r
review = UIReview(r, pkgversion, self.logged_in_person,
self.useful_votes, is_first_for_version)
review.show_all()
self.vbox.pack_start(review, True, True, 0)
def _be_the_first_to_review(self):
s = _('Be the first to review it')
self.new_review.set_label(s)
self.vbox.pack_start(NoReviewYetWriteOne(), True, True, 0)
self.vbox.show_all()
def _install_to_review(self):
s = ('%s' %
_("You need to install this before you can review it"))
self.install_first_label = Gtk.Label(label=s)
self.install_first_label.set_use_markup(True)
self.install_first_label.set_alignment(1.0, 0.5)
self.reviews_info_hbox.pack_start(
self.install_first_label, False, False, 0)
self.install_first_label.show()
# FIXME: this needs to be smarter in the future as we will
# not allow multiple reviews for the same software version
def _any_reviews_current_user(self):
for review in self.reviews:
if self.logged_in_person == review.reviewer_username:
return True
return False
def _add_no_network_connection_msg(self):
title = _('No network connection')
msg = _('Connect to the Internet to see more reviews.')
m = EmbeddedMessage(title, msg, 'network-offline')
self.vbox.pack_start(m, True, True, 0)
return m
def _clear_vbox(self, vbox):
children = vbox.get_children()
for child in children:
child.destroy()
# FIXME: instead of clear/add_reviews/configure_reviews_ui we should
# provide a single show_reviews(reviews_data_list)
def configure_reviews_ui(self):
""" this needs to be called after add_reviews, it will actually
show the reviews
"""
#print 'Review count: %s' % len(self.reviews)
try:
self.install_first_label.hide()
except AttributeError:
pass
self._clear_vbox(self.vbox)
# network sensitive stuff, only show write_review if connected,
# add msg about offline cache usage if offline
is_connected = network_state_is_connected()
self.no_network_msg = self._add_no_network_connection_msg()
# only show new_review for installed stuff
is_installed = (self._parent.app_details and
self._parent.app_details.pkg_state == PkgStates.INSTALLED)
# show/hide new review button
if is_installed:
self.new_review.show()
else:
self.new_review.hide()
# if there are no reviews, the install to review text appears
# where the reviews usually are (LP #823255)
if self.reviews:
self._install_to_review()
# always hide spinner and call _fill (fine if there is nothing to do)
self.hide_spinner()
self._fill()
if self.reviews:
# adjust label if we have reviews
if self._any_reviews_current_user():
self.new_review.hide()
else:
self.new_review.set_label(_("Write your own review"))
else:
# no reviews, either offer to write one or show "none"
if (self.get_active_review_language() != 'any' and
self.global_review_stats and
self.global_review_stats.ratings_total > 0):
self.vbox.pack_start(NoReviewRelaxLanguage(), True, True, 0)
elif is_installed and is_connected:
self._be_the_first_to_review()
else:
self.vbox.pack_start(NoReviewYet(), True, True, 0)
# aaronp: removed check to see if the length of reviews is divisible by
# the batch size to allow proper fixing of LP: #794060 as when a review
# is submitted and appears in the list, the pagination will break this
# check and make it unreliable
# if self.reviews and len(self.reviews) % REVIEWS_BATCH_PAGE_SIZE == 0:
if self.reviews:
button = Gtk.Button(_("Check for more reviews"))
button.connect("clicked", self._on_more_reviews_clicked)
button.show()
self.vbox.pack_start(button, False, False, 0)
# always run this here to make update the current ui based on the
# network state
self._on_network_state_change()
def _on_more_reviews_clicked(self, button):
# remove button and emit signal
self.vbox.remove(button)
self.emit("more-reviews-clicked")
def _on_different_review_language_clicked(self, combo):
language = self.get_active_review_language()
# clean reviews so that we can show the new language
self.clear()
self.emit("different-review-language-clicked", language)
def get_active_review_language(self):
model = self.review_language.get_model()
language = model[self.review_language.get_active_iter()][COL_LANGCODE]
return language
def get_all_review_ids(self):
ids = []
for review in self.reviews:
ids.append(review.id)
return ids
def add_review(self, review):
self.reviews.append(review)
def replace_review(self, review):
for r in self.reviews:
if r.id == review.id:
pos = self.reviews.index(r)
self.reviews.remove(r)
self.reviews.insert(pos, review)
break
def remove_review(self, review):
for r in self.reviews:
if r.id == review.id:
self.reviews.remove(r)
break
def clear(self):
self.reviews = []
for review in self.vbox:
review.destroy()
self.new_review.hide()
# FIXME: ideally we would have "{show,hide}_loading_notice()" to
# easily allow changing from e.g. spinner to text
def show_spinner_with_message(self, message):
try:
self.install_first_label.hide()
except AttributeError:
pass
a = Gtk.Alignment.new(0.5, 0.5, 1.0, 1.0)
hb = Gtk.HBox(spacing=12)
hb.show()
a.add(hb)
a.show()
spinner = Gtk.Spinner()
spinner.start()
spinner.show()
hb.pack_start(spinner, False, False, 0)
l = Gtk.Label()
l.set_markup(message)
l.set_use_markup(True)
l.show()
hb.pack_start(l, False, False, 0)
self.vbox.pack_start(a, False, False, 0)
self.vbox.show()
def hide_spinner(self):
for child in self.vbox.get_children():
if isinstance(child, Gtk.Alignment):
child.destroy()
def draw(self, cr, a):
for r in self.vbox:
if isinstance(r, (UIReview)):
r.draw(cr, r.get_allocation())
class UIReview(Gtk.VBox):
""" the UI for a individual review including all button to mark
useful/inappropriate etc
"""
def __init__(self, review_data=None, app_version=None,
logged_in_person=None, useful_votes=None,
first_for_version=True):
Gtk.VBox.__init__(self)
self.set_spacing(StockEms.SMALL)
self.version_label = Gtk.Label()
self.version_label.set_alignment(0, 0.5)
self.header = Gtk.HBox()
self.header.set_spacing(StockEms.MEDIUM)
self.body = Gtk.VBox()
self.footer = Gtk.HBox()
self.useful = None
self.yes_like = None
self.no_like = None
self.status_box = Gtk.HBox()
self.delete_status_box = Gtk.HBox()
self.delete_error_img = Gtk.Image()
self.delete_error_img.set_from_stock(
Gtk.STOCK_DIALOG_ERROR,
Gtk.IconSize.SMALL_TOOLBAR)
self.submit_error_img = Gtk.Image()
self.submit_error_img.set_from_stock(
Gtk.STOCK_DIALOG_ERROR,
Gtk.IconSize.SMALL_TOOLBAR)
self.submit_status_spinner = Gtk.Spinner()
self.submit_status_spinner.set_size_request(12, 12)
self.delete_status_spinner = Gtk.Spinner()
self.delete_status_spinner.set_size_request(12, 12)
self.acknowledge_error = Gtk.Button()
label = Gtk.Label()
label.set_markup('%s' % _("OK"))
self.acknowledge_error.add(label)
self.delete_acknowledge_error = Gtk.Button()
delete_label = Gtk.Label()
delete_label.set_markup('%s' % _("OK"))
self.delete_acknowledge_error.add(delete_label)
self.usefulness_error = False
self.delete_error = False
self.modify_error = False
if first_for_version:
self.pack_start(self.version_label, False, False, 0)
self.pack_start(self.header, False, False, 0)
self.pack_start(self.body, False, False, 0)
self.pack_start(self.footer, False, False, StockEms.SMALL)
self.logged_in_person = logged_in_person
self.person = None
self.id = None
self.useful_votes = useful_votes
self._allocation = None
if review_data:
self._build(review_data,
app_version,
logged_in_person,
useful_votes)
def _on_report_abuse_clicked(self, button):
reviews = self.get_ancestor(UIReviewsList)
if reviews:
reviews.emit("report-abuse", self.id)
def _on_modify_clicked(self, button):
reviews = self.get_ancestor(UIReviewsList)
if reviews:
reviews.emit("modify-review", self.id)
def _on_useful_clicked(self, btn, is_useful):
reviews = self.get_ancestor(UIReviewsList)
if reviews:
self._usefulness_ui_update('progress')
reviews.emit("submit-usefulness", self.id, is_useful)
def _on_error_acknowledged(self, button, current_user_reviewer,
useful_total, useful_favorable):
self.usefulness_error = False
self._usefulness_ui_update('renew', current_user_reviewer,
useful_total, useful_favorable)
def _usefulness_ui_update(self, type, current_user_reviewer=False,
useful_total=0, useful_favorable=0):
self._hide_usefulness_elements()
#print "_usefulness_ui_update: %s" % type
if type == 'renew':
self._build_usefulness_ui(current_user_reviewer, useful_total,
useful_favorable, self.useful_votes)
return
if type == 'progress':
self.status_label = Gtk.Label.new(
"%s" % _(u"Submitting now\u2026"))
self.status_label.set_use_markup(True)
self.status_box.pack_start(self.submit_status_spinner, False,
False, 0)
self.submit_status_spinner.show()
self.submit_status_spinner.start()
self.status_label.set_padding(2, 0)
self.status_box.pack_start(self.status_label, False, False, 0)
self.status_label.show()
if type == 'error':
self.submit_error_img.show()
self.status_label = Gtk.Label.new(
"%s" % _("Error submitting usefulness"))
self.status_label.set_use_markup(True)
self.status_box.pack_start(self.submit_error_img, False, False, 0)
self.status_label.set_padding(2, 0)
self.status_box.pack_start(self.status_label, False, False, 0)
self.status_label.show()
self.acknowledge_error.show()
self.status_box.pack_start(self.acknowledge_error, False, False, 0)
self.acknowledge_error.connect('clicked',
self._on_error_acknowledged, current_user_reviewer,
useful_total, useful_favorable)
self.status_box.show()
self.footer.pack_start(self.status_box, False, False, 0)
def _hide_usefulness_elements(self):
""" hide all usefulness elements """
for attr in ["useful", "yes_like", "no_like", "submit_status_spinner",
"submit_error_img", "status_box", "status_label",
"acknowledge_error", "yes_no_separator"
]:
widget = getattr(self, attr, None)
if widget:
widget.hide()
def _get_datetime_from_review_date(self, raw_date_str):
# example raw_date str format: 2011-01-28 19:15:21
return datetime.datetime.strptime(raw_date_str, '%Y-%m-%d %H:%M:%S')
def _delete_ui_update(self, type, current_user_reviewer=False,
action=None):
self._hide_delete_elements()
if type == 'renew':
self._build_delete_flag_ui(current_user_reviewer)
return
if type == 'progress':
self.delete_status_spinner.start()
self.delete_status_spinner.show()
self.delete_status_label = Gtk.Label(
"%s" % _(u"Deleting now\u2026"))
self.delete_status_box.pack_start(self.delete_status_spinner,
False, False, 0)
self.delete_status_label.set_use_markup(True)
self.delete_status_label.set_padding(2, 0)
self.delete_status_box.pack_start(self.delete_status_label, False,
False, 0)
self.delete_status_label.show()
if type == 'error':
self.delete_error_img.show()
# build full strings for easier i18n
if action == 'deleting':
s = _("Error deleting review")
elif action == 'modifying':
s = _("Error modifying review")
else:
# or unknown error, but we are in string freeze,
# should never happen anyway
s = _("Internal Error")
self.delete_status_label = Gtk.Label(
"%s" % s)
self.delete_status_box.pack_start(self.delete_error_img,
False, False, 0)
self.delete_status_label.set_use_markup(True)
self.delete_status_label.set_padding(2, 0)
self.delete_status_box.pack_start(self.delete_status_label,
False, False, 0)
self.delete_status_label.show()
self.delete_acknowledge_error.show()
self.delete_status_box.pack_start(self.delete_acknowledge_error,
False, False, 0)
self.delete_acknowledge_error.connect('clicked',
self._on_delete_error_acknowledged, current_user_reviewer)
self.delete_status_box.show()
self.footer.pack_end(self.delete_status_box, False, False, 0)
def _on_delete_clicked(self, btn):
reviews = self.get_ancestor(UIReviewsList)
if reviews:
self._delete_ui_update('progress')
reviews.emit("delete-review", self.id)
def _on_delete_error_acknowledged(self, button, current_user_reviewer):
self.delete_error = False
self._delete_ui_update('renew', current_user_reviewer)
def _hide_delete_elements(self):
""" hide all delete elements """
for attr in ["complain", "edit", "delete", "delete_status_spinner",
"delete_error_img", "delete_status_box",
"delete_status_label", "delete_acknowledge_error",
"flagbox"
]:
o = getattr(self, attr, None)
if o:
o.hide()
def _build(self, review_data, app_version, logged_in_person, useful_votes):
# all the attributes of review_data may need markup escape,
# depending on if they are used as text or markup
self.id = review_data.id
self.person = review_data.reviewer_username
displayname = review_data.reviewer_displayname
# example raw_date str format: 2011-01-28 19:15:21
cur_t = self._get_datetime_from_review_date(review_data.date_created)
review_version = review_data.version
self.useful_total = useful_total = review_data.usefulness_total
useful_favorable = review_data.usefulness_favorable
useful_submit_error = review_data.usefulness_submit_error
delete_error = review_data.delete_error
modify_error = review_data.modify_error
# upstream version
version = GLib.markup_escape_text(upstream_version(review_version))
# default string
version_string = _("For version %(version)s") % {
'version': version,
}
# If its for the same version, show it as such
if (review_version and
app_version and
upstream_version_compare(review_version, app_version) == 0):
version_string = _("For this version (%(version)s)") % {
'version': version,
}
m = '%s'
self.version_label.set_markup(m % version_string)
m = self._whom_when_markup(self.person, displayname, cur_t)
who_when = Gtk.Label()
who_when.set_name("subtle-label")
who_when.set_justify(Gtk.Justification.RIGHT)
who_when.set_markup(m)
summary = Gtk.Label()
try:
s = GLib.markup_escape_text(review_data.summary.encode("utf-8"))
summary.set_markup('%s' % s)
except Exception:
LOG.exception("_build() failed")
summary.set_text("Error parsing summary")
summary.set_ellipsize(Pango.EllipsizeMode.END)
summary.set_selectable(True)
summary.set_alignment(0, 0.5)
text = Gtk.Label()
text.set_text(review_data.review_text)
text.set_line_wrap(True)
text.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR)
text.set_selectable(True)
text.set_alignment(0, 0)
stars = Star()
stars.set_rating(review_data.rating)
a = Gtk.Alignment.new(0.5, 0.5, 0, 0)
a.add(stars)
self.header.pack_start(a, False, False, 0)
self.header.pack_start(summary, False, False, 0)
self.header.pack_end(who_when, False, False, 0)
self.body.pack_start(text, False, False, 0)
current_user_reviewer = False
if self.person == self.logged_in_person:
current_user_reviewer = True
self._build_usefulness_ui(current_user_reviewer, useful_total,
useful_favorable, useful_votes,
useful_submit_error)
self.flagbox = Gtk.HBox()
self.flagbox.set_spacing(4)
self._build_delete_flag_ui(current_user_reviewer, delete_error,
modify_error)
self.footer.pack_end(self.flagbox, False, False, 0)
# connect network signals
self.connect("realize", lambda w: self._on_network_state_change())
watcher = get_network_watcher()
watcher.connect(
"changed", lambda w, s: self._on_network_state_change())
def _build_usefulness_ui(self, current_user_reviewer, useful_total,
useful_favorable, useful_votes,
usefulness_submit_error=False):
if usefulness_submit_error:
self._usefulness_ui_update('error', current_user_reviewer,
useful_total, useful_favorable)
else:
already_voted = useful_votes.check_for_usefulness(self.id)
#get correct label based on retrieved usefulness totals and
# if user is reviewer
self.useful = self._get_usefulness_label(
current_user_reviewer, useful_total, useful_favorable,
already_voted)
self.useful.set_use_markup(True)
#vertically centre so it lines up with the Yes and No buttons
self.useful.set_alignment(0, 0.5)
self.useful.show()
self.footer.pack_start(self.useful, False, False, 3)
# add here, but only populate if its not the own review
self.likebox = Gtk.HBox()
if already_voted is None and not current_user_reviewer:
m = '%s'
self.yes_like = Link(m % _('Yes'))
self.yes_like.set_name("subtle-label")
self.no_like = Link(m % _('No'))
self.no_like.set_name("subtle-label")
self.yes_like.connect('clicked', self._on_useful_clicked, True)
self.no_like.connect('clicked', self._on_useful_clicked, False)
self.yes_no_separator = Gtk.Label()
self.yes_no_separator.set_name("subtle-label")
self.yes_no_separator.set_markup(m % _('/'))
self.yes_like.show()
self.no_like.show()
self.yes_no_separator.show()
self.likebox.set_spacing(4)
self.likebox.pack_start(self.yes_like, False, False, 0)
self.likebox.pack_start(self.yes_no_separator, False, False, 0)
self.likebox.pack_start(self.no_like, False, False, 0)
self.footer.pack_start(self.likebox, False, False, 0)
def _on_network_state_change(self):
""" show/hide widgets based on network connection state """
# FIXME: make this dynamic show/hide on network changes
# FIXME2: make ti actually work, later show_all() kill it
# currently
if network_state_is_connected():
self.likebox.show()
self.useful.show()
self.flagbox.show()
else:
self.likebox.hide()
# we hide the useful box because if its there it says something
# like "10 people found this useful. Did you?" but you can't
# actually submit anything without network
self.useful.hide()
self.flagbox.hide()
def _get_usefulness_label(self, current_user_reviewer,
useful_total, useful_favorable, already_voted):
'''returns Gtk.Label() to be used as usefulness label depending
on passed in parameters
'''
if already_voted is None:
if useful_total == 0 and current_user_reviewer:
s = ""
elif useful_total == 0:
# no votes for the review yet
s = _("Was this review helpful?")
elif current_user_reviewer:
# user has already voted for the review
s = gettext.ngettext(
"%(useful_favorable)s of %(useful_total)s people "
"found this review helpful.",
"%(useful_favorable)s of %(useful_total)s people "
"found this review helpful.",
useful_total) % {
'useful_total': useful_total,
'useful_favorable': useful_favorable,
}
else:
# user has not already voted for the review
s = gettext.ngettext(
"%(useful_favorable)s of %(useful_total)s people "
"found this review helpful. Did you?",
"%(useful_favorable)s of %(useful_total)s people "
"found this review helpful. Did you?",
useful_total) % {
'useful_total': useful_total,
'useful_favorable': useful_favorable,
}
else:
#only display these special strings if the user voted either way
if already_voted:
if useful_total == 1:
s = _("You found this review helpful.")
else:
s = gettext.ngettext(
"%(useful_favorable)s of %(useful_total)s people "
"found this review helpful, including you",
"%(useful_favorable)s of %(useful_total)s people "
"found this review helpful, including you.",
useful_total) % {
'useful_total': useful_total,
'useful_favorable': useful_favorable,
}
else:
if useful_total == 1:
s = _("You found this review unhelpful.")
else:
s = gettext.ngettext(
"%(useful_favorable)s of %(useful_total)s people "
"found this review helpful; you did not.",
"%(useful_favorable)s of %(useful_total)s people "
"found this review helpful; you did not.",
useful_total) % {
'useful_total': useful_total,
'useful_favorable': useful_favorable,
}
m = '%s'
label = Gtk.Label()
label.set_name("subtle-label")
label.set_markup(m % s)
return label
def _build_delete_flag_ui(self, current_user_reviewer, delete_error=False,
modify_error=False):
if delete_error:
self._delete_ui_update('error', current_user_reviewer, 'deleting')
elif modify_error:
self._delete_ui_update('error', current_user_reviewer, 'modifying')
else:
m = '%s'
if current_user_reviewer:
self.edit = Link(m % _('Edit'))
self.edit.set_name("subtle-label")
self.delete = Link(m % _('Delete'))
self.delete.set_name("subtle-label")
self.flagbox.pack_start(self.edit, False, False, 0)
self.flagbox.pack_start(self.delete, False, False, 0)
self.edit.connect('clicked', self._on_modify_clicked)
self.delete.connect('clicked', self._on_delete_clicked)
else:
# Translators: This link is for flagging a review as
# inappropriate. To minimize repetition, if at all possible,
# keep it to a single word. If your language has an obvious
# verb, it won't need a question mark.
self.complain = Link(m % _('Inappropriate?'))
self.complain.set_name("subtle-label")
self.flagbox.pack_start(self.complain, False, False, 0)
self.complain.connect('clicked', self._on_report_abuse_clicked)
self.flagbox.show_all()
def _whom_when_markup(self, person, displayname, cur_t):
nice_date = get_nice_date_string(cur_t)
#dt = datetime.datetime.utcnow() - cur_t
# prefer displayname if available
correct_name = displayname or person
if person == self.logged_in_person:
m = '%s (%s), %s' % (
GLib.markup_escape_text(utf8(correct_name)),
# TRANSLATORS: displayed in a review after the persons name,
# e.g. "Jane Smith (that's you), 2011-02-11"
utf8(_(u"that\u2019s you")),
GLib.markup_escape_text(utf8(nice_date)))
else:
try:
m = '%s, %s' % (
GLib.markup_escape_text(correct_name.encode("utf-8")),
GLib.markup_escape_text(nice_date))
except Exception:
LOG.exception("_who_when_markup failed")
m = "Error parsing name"
return m
def draw(self, widget, cr):
pass
class EmbeddedMessage(UIReview):
def __init__(self, title='', message='', icon_name=''):
UIReview.__init__(self)
self.label = None
self.image = None
a = Gtk.Alignment.new(0.5, 0.5, 1.0, 1.0)
self.body.pack_start(a, False, False, 0)
hb = Gtk.HBox()
hb.set_spacing(12)
a.add(hb)
if icon_name:
self.image = Gtk.Image.new_from_icon_name(icon_name,
Gtk.IconSize.DIALOG)
hb.pack_start(self.image, False, False, 0)
self.label = Gtk.Label()
self.label.set_line_wrap(True)
self.label.set_alignment(0, 0.5)
if title:
self.label.set_markup('%s\n%s' %
(title, message))
else:
self.label.set_markup(message)
hb.pack_start(self.label, True, True, 0)
self.show_all()
def draw(self, cr, a):
pass
class NoReviewRelaxLanguage(EmbeddedMessage):
""" represents if there are no reviews yet and the app is not installed """
def __init__(self, *args, **kwargs):
# TRANSLATORS: displayed if there are no reviews for the app in
# the current language, but there are some in other
# languages
title = _("This app has not been reviewed yet in your language")
msg = _('Try selecting a different language, or even "Any language"'
' in the language dropdown')
EmbeddedMessage.__init__(self, title, msg)
class NoReviewYet(EmbeddedMessage):
""" represents if there are no reviews yet and the app is not installed """
def __init__(self, *args, **kwargs):
# TRANSLATORS: displayed if there are no reviews for the app yet
# and the user does not have it installed
title = _("This app has not been reviewed yet")
msg = _('You need to install this before you can review it')
EmbeddedMessage.__init__(self, title, msg)
class NoReviewYetWriteOne(EmbeddedMessage):
""" represents if there are no reviews yet and the app is installed """
def __init__(self, *args, **kwargs):
# TRANSLATORS: displayed if there are no reviews yet and the user
# has the app installed
title = _('Got an opinion?')
msg = _('Be the first to contribute a review for this application')
EmbeddedMessage.__init__(self, title, msg, 'text-editor')