25
25
from softwarecenter.ui.gtk3.em import StockEms
26
26
from softwarecenter.ui.gtk3.widgets.containers import (FramedHeaderBox,
28
28
from softwarecenter.ui.gtk3.utils import get_parent_xid
29
29
from softwarecenter.db.categories import (RecommendedForYouCategory,
30
30
AppRecommendationsCategory)
31
from softwarecenter.backend.installbackend import get_install_backend
31
32
from softwarecenter.backend.recagent import RecommenderAgent
32
from softwarecenter.backend.login_sso import get_sso_backend
33
from softwarecenter.backend.login import get_login_backend
33
34
from softwarecenter.backend.ubuntusso import get_ubuntu_sso_backend
34
from softwarecenter.enums import SOFTWARE_CENTER_NAME_KEYRING
35
from softwarecenter.enums import (
36
LOBBY_RECOMMENDATIONS_CAROUSEL_LIMIT,
37
DETAILS_RECOMMENDATIONS_CAROUSEL_LIMIT,
38
SOFTWARE_CENTER_NAME_KEYRING,
39
RecommenderFeedbackActions,
35
42
from softwarecenter.utils import utf8
36
43
from softwarecenter.netstatus import network_state_is_connected
53
def __init__(self, catview):
54
61
FramedHeaderBox.__init__(self)
55
# FIXME: we only need the catview for "add_titles_to_flowgrid"
56
# and "on_category_clicked" so we should be able to
57
# extract this to a "leaner" widget
58
self.catview = catview
60
"application-activated", self._on_application_activated)
61
62
self.recommender_agent = RecommenderAgent()
63
# keep track of applications that have been viewed via a
64
# recommendation so that we can detect when a recommended app
66
self.recommended_apps_viewed = set()
67
self.backend = get_install_backend()
68
self.backend.connect("transaction-started",
69
self._on_transaction_started)
70
self.backend.connect("transaction-finished",
71
self._on_transaction_finished)
63
def _on_application_activated(self, catview, app):
73
def _on_application_activated(self, tile, app):
64
74
self.emit("application-activated", app)
75
# we only report on items if the user has opted-in to the
76
# recommendations service
77
if self.recommender_agent.is_opted_in():
78
self.recommended_apps_viewed.add(app.pkgname)
79
if network_state_is_connected():
80
# let the recommendations service know that a
81
# recommended item has been viewed (if it is
82
# subsequently installed we will send an additional
83
# signal to indicate that, in on_transaction_finished)
84
# (there is no need to monitor for an error, etc., for this)
85
self.recommender_agent.post_implicit_feedback(
87
RecommenderFeedbackActions.VIEWED)
89
def _on_transaction_started(self, backend, pkgname, appname, trans_id,
91
if (trans_type != TransactionTypes.INSTALL and
92
pkgname in self.recommended_apps_viewed):
93
# if the transaction is not an installation we don't want to
94
# track it as a recommended item
95
self.recommended_apps_viewed.remove(pkgname)
97
def _on_transaction_finished(self, backend, result):
98
if result.pkgname in self.recommended_apps_viewed:
99
self.recommended_apps_viewed.remove(result.pkgname)
100
if network_state_is_connected():
101
# let the recommendations service know that a
102
# recommended item has been successfully installed
103
# (there is no need to monitor for an error, etc., for this)
104
self.recommender_agent.post_implicit_feedback(
106
RecommenderFeedbackActions.INSTALLED)
66
108
def _on_recommender_agent_error(self, agent, msg):
67
109
LOG.warn("Error while accessing the recommender agent: %s" % msg)
68
# TODO: temporary, instead we will display cached recommendations here
69
self._hide_recommended_for_you_panel()
71
def _hide_recommended_for_you_panel(self):
110
# this can happen if:
111
# - there is a real error from the agent
112
# - no cached data is available
113
# hide the panel on an error
88
139
if self.recommender_agent.is_opted_in():
89
140
self._update_recommended_for_you_content()
91
self._hide_recommended_for_you_panel()
93
144
def _update_recommended_for_you_content(self):
94
145
# destroy the old content to ensure we don't see it twice
95
146
if self.recommended_for_you_content:
96
147
self.recommended_for_you_content.destroy()
97
148
# add the new stuff
98
self.recommended_for_you_content = FlowableGrid()
149
self.recommended_for_you_content = TileGrid()
150
self.recommended_for_you_content.connect(
151
"application-activated", self._on_application_activated)
99
152
self.add(self.recommended_for_you_content)
100
153
self.spinner_notebook.show_spinner(_(u"Receiving recommendations…"))
101
154
# get the recommendations from the recommender agent
102
155
self.recommended_for_you_cat = RecommendedForYouCategory(
103
157
subcategory=self.subcategory)
104
158
self.recommended_for_you_cat.connect(
110
164
def _on_recommended_for_you_agent_refresh(self, cat):
111
165
self.header_implements_more_button()
112
docs = cat.get_documents(self.catview.db)
166
self.more.connect("clicked",
167
self._on_more_button_clicked,
168
self.recommended_for_you_cat)
169
docs = cat.get_documents(self.db)
113
170
# display the recommendedations
114
171
if len(docs) > 0:
115
self.catview._add_tiles_to_flowgrid(docs,
116
self.recommended_for_you_content, 12)
172
self.recommended_for_you_content.add_tiles(
173
self.properties_helper,
175
LOBBY_RECOMMENDATIONS_CAROUSEL_LIMIT)
117
176
self.recommended_for_you_content.show_all()
118
177
self.spinner_notebook.hide_spinner()
119
self.more.connect('clicked',
120
self.catview.on_category_clicked,
122
178
self.header.queue_draw()
125
181
# hide the panel if we have no recommendations to show
126
self._hide_recommended_for_you_panel()
184
def _on_more_button_clicked(self, btn, category):
185
self.emit("more-button-clicked", category)
129
188
class RecommendationsPanelLobby(RecommendationsPanelCategory):
146
205
TURN_ON_RECOMMENDATIONS_TEXT = _(u"Turn On Recommendations")
147
# FIXME: This will be the default text once LP: #986437 is approved and
148
# ready to be merged/SRU'd
149
206
RECOMMENDATIONS_OPT_IN_TEXT = _(u"To make recommendations, "
150
207
"Ubuntu Software Center "
151
208
"will occasionally send to Canonical a list "
152
209
"of software currently installed.")
210
NO_NETWORK_RECOMMENDATIONS_TEXT = _(u"Recommendations will appear "
154
def __init__(self, catview):
155
RecommendationsPanel.__init__(self, catview)
213
def __init__(self, db, properties_helper):
214
RecommendationsPanel.__init__(self)
216
self.properties_helper = properties_helper
156
217
self.subcategory = None
157
218
self.set_header_label(_(u"Recommended For You"))
158
219
self.recommended_for_you_content = None
220
# .is_opted_in() means either "successfully opted-in" or
221
# "requested opt-in" (but not done yet)
159
222
if self.recommender_agent.is_opted_in():
160
self._try_sso_login()
223
if network_state_is_connected():
224
self._try_sso_login()
226
if self.recommender_agent.opt_in_requested:
227
# the user has opted in but has not yet completed the
228
# initial recommender profile upload, therefore there
229
# are no cached values available yet to display
230
self._show_no_network_view()
232
# display cached recommendations
233
self._update_recommended_for_you_content()
162
235
self._show_opt_in_view()
164
237
def _show_opt_in_view(self):
166
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, StockEms.MEDIUM)
167
vbox.set_border_width(StockEms.MEDIUM)
168
self.opt_in_vbox = vbox # for tests
169
self.recommended_for_you_content = vbox # hook it up to the rest
239
self.recommended_for_you_content = Gtk.Box.new(
240
Gtk.Orientation.VERTICAL,
242
self.recommended_for_you_content.set_border_width(StockEms.MEDIUM)
171
243
self.add(self.recommended_for_you_content)
174
button = Gtk.Button(_(self.TURN_ON_RECOMMENDATIONS_TEXT))
175
button.connect("clicked", self._on_opt_in_button_clicked)
246
self.opt_in_button = Gtk.Button(_(self.TURN_ON_RECOMMENDATIONS_TEXT))
247
self.opt_in_button.connect("clicked", self._on_opt_in_button_clicked)
176
248
hbox = Gtk.Box(Gtk.Orientation.HORIZONTAL)
177
hbox.pack_start(button, False, False, 0)
178
vbox.pack_start(hbox, False, False, 0)
179
self.opt_in_button = button # for tests
249
hbox.pack_start(self.opt_in_button, False, False, 0)
250
self.recommended_for_you_content.pack_start(hbox, False, False, 0)
182
253
text = _(self.RECOMMENDATIONS_OPT_IN_TEXT)
184
label.set_use_markup(True)
185
markup = '<small>%s</small>'
186
label.set_name("subtle-label")
187
label.set_markup(markup % text)
188
label.set_alignment(0, 0.5)
189
label.set_line_wrap(True)
190
vbox.pack_start(label, False, False, 0)
254
self.opt_in_label = self._create_opt_in_label(text)
255
self.recommended_for_you_content.pack_start(self.opt_in_label,
258
def _show_no_network_view(self):
259
# display network not available message
260
if not self.recommended_for_you_content:
261
self.recommended_for_you_content = Gtk.Box.new(
262
Gtk.Orientation.VERTICAL,
264
self.recommended_for_you_content.set_border_width(StockEms.MEDIUM)
265
self.add(self.recommended_for_you_content)
266
text = _(self.NO_NETWORK_RECOMMENDATIONS_TEXT)
267
self.opt_in_label = self._create_opt_in_label(text)
268
self.recommended_for_you_content.pack_start(self.opt_in_label,
271
self.opt_in_button.hide()
272
text = _(self.NO_NETWORK_RECOMMENDATIONS_TEXT)
273
self.opt_in_label.set_markup(self.opt_in_label_markup % text)
275
def _create_opt_in_label(self, label_text):
276
opt_in_label = Gtk.Label()
277
opt_in_label.set_use_markup(True)
278
self.opt_in_label_markup = '<small>%s</small>'
279
opt_in_label.set_name("subtle-label")
280
opt_in_label.set_markup(self.opt_in_label_markup % label_text)
281
opt_in_label.set_alignment(0, 0.5)
282
opt_in_label.set_line_wrap(True)
192
285
def _on_opt_in_button_clicked(self, button):
193
286
self.opt_in_to_recommendations_service()
319
419
Panel for use in the details view to display recommendations for a given
322
def __init__(self, catview):
323
RecommendationsPanel.__init__(self, catview)
422
def __init__(self, db, properties_helper):
423
RecommendationsPanel.__init__(self)
425
self.properties_helper = properties_helper
324
426
self.set_header_label(_(u"People Also Installed"))
325
self.app_recommendations_content = FlowableGrid()
427
self.app_recommendations_content = TileGrid()
428
self.app_recommendations_content.connect(
429
"application-activated", self._on_application_activated)
326
430
self.add(self.app_recommendations_content)
328
432
def set_pkgname(self, pkgname):
342
448
self._on_recommender_agent_error)
344
450
def _on_app_recommendations_agent_refresh(self, cat):
345
docs = cat.get_documents(self.catview.db)
451
docs = cat.get_documents(self.db)
346
452
# display the recommendations
347
453
if len(docs) > 0:
348
self.catview._add_tiles_to_flowgrid(docs,
349
self.app_recommendations_content, 3)
454
self.app_recommendations_content.add_tiles(
455
self.properties_helper,
457
DETAILS_RECOMMENDATIONS_CAROUSEL_LIMIT)
351
459
self.spinner_notebook.hide_spinner()
353
self._hide_app_recommendations_panel()
355
def _on_recommender_agent_error(self, agent, msg):
356
LOG.warn("Error while accessing the recommender agent for the "
357
"details view recommendations: %s" % msg)
358
# TODO: temporary, instead we will display cached recommendations here
359
self._hide_app_recommendations_panel()
361
def _hide_app_recommendations_panel(self):
366
464
class RecommendationsOptInDialog(Gtk.MessageDialog):