16
16
# this program; if not, write to the Free Software Foundation, Inc.,
17
17
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
from gettext import gettext as _
19
23
from gi.repository import Gtk
20
24
from gi.repository import Gdk
21
25
from gi.repository import GObject
22
26
from gi.repository import GdkPixbuf
27
from gi.repository import WebKit
24
29
from softwarecenter.utils import SimpleFileDownloader
27
class Exhibit(Gtk.EventBox):
28
""" a single exhibit ui element """
31
"clicked" : (GObject.SignalFlags.RUN_LAST,
37
def __init__(self, exhibit, right_pixel_cutoff=0):
30
from softwarecenter.ui.gtk3.em import StockEms
31
from softwarecenter.ui.gtk3.shapes import Circle
32
import softwarecenter.paths
38
<style type="text/css">
44
text-shadow:0em 0em 0.075em black;
53
text-shadow:0em 0em 0.075em black;
61
<img style="position:absolute; top:0; left:0;" src="%(banner_url)s">
62
<div class="banner_text">%(title)s</div>
63
<div class="banner_subtext"> %(subtitle)s</div>
68
class DefaultExhibit(object):
71
self.package_names = ""
72
self.title_translated = "Default exhibit"
74
self.banner_url = "file://%s" % (os.path.abspath(os.path.join(softwarecenter.paths.datadir, "default_banner/fallback.png")))
75
self.html = EXHIBIT_HTML % {
76
'banner_url' : self.banner_url,
77
'title' : _("Welcome to the Ubuntu Software Center"),
78
'subtitle' : _("Its a new day"),
80
# we should extract this automatically from the html
81
#self.atk_name = _("Default Banner")
82
#self.atk_description = _("You see this banner because you have no cached banners")
84
class FeaturedExhibit(object):
87
self.package_names = "armagetronad,calibre,cheese,homebank,stellarium,gimp,inkscape,blender,audacity,gufw,frozen-bubble,fretsonfire,moovida,liferea,arista,gtg,freeciv-client-gtk,supertuxkart,tumiki-fighters,tuxpaint,webservice-office-zoho"
88
self.title_translated = "Featured exhibit"
90
self.banner_url = "file://%s" % (os.path.abspath(os.path.join(softwarecenter.paths.datadir, "default_banner/fallback2.png")))
91
self.html = EXHIBIT_HTML % {
92
'banner_url' : self.banner_url,
93
'title' : _("Stuff we like"),
94
'subtitle' : _("Its just great, try it!"),
96
# we should extract this automatically from the html
97
#self.atk_name = _("Default Banner")
98
#self.atk_description = _("You see this banner because you have no cached banners")
101
class _HtmlRenderer(Gtk.OffscreenWindow):
104
"render-finished" : (GObject.SignalFlags.RUN_LAST,
112
Gtk.OffscreenWindow.__init__(self)
113
self.view = WebKit.WebView()
114
settings = self.view.get_settings()
115
settings.set_property("enable-java-applet", False)
116
settings.set_property("enable-plugins", False)
117
settings.set_property("enable-scripts", False)
118
self.view.set_size_request(-1, ExhibitBanner.MAX_HEIGHT)
121
self.loader = SimpleFileDownloader()
122
self.loader.connect("file-download-complete",
123
self.on_download_complete)
125
self.view.connect("notify::load-status", self._on_load_status)
128
def _on_load_status(self, view, prop):
129
if view.get_property("load-status") == WebKit.LoadStatus.FINISHED:
130
# this needs to run with a timeout because otherwise the
131
# status is emited before the offscreen image is finihsed
132
GObject.timeout_add(1, lambda: self.emit("render-finished"))
134
def on_download_complete(self, loader, path):
135
image_name = os.path.basename(path)
136
cache_dir = os.path.dirname(path)
137
if hasattr(self.exhibit, "html") and self.exhibit.html:
138
html = self.exhibit.html
140
# special case, if there is no title, then the package_names
141
# is actually just a single string
142
if (not hasattr(self.exhibit, "title_translated") or
143
not self.exhibit.title_translated):
144
self.exhibit.title_translated = self.exhibit.package_names
145
html = EXHIBIT_HTML % { 'banner_url' : self.exhibit.banner_url,
146
'title' : self.exhibit.title_translated,
149
html = html.replace(self.exhibit.banner_url, image_name)
150
self.view.load_string(html, "text/html", "UTF-8",
151
"file:///%s/" % cache_dir)
154
def set_exhibit(self, exhibit):
155
self.exhibit = exhibit
156
self.loader.download_file(exhibit.banner_url,
158
simple_quoting_for_webkit=True)
162
class ExhibitButton(Gtk.Button):
165
Gtk.Button.__init__(self)
166
self.DROPSHADOW = GdkPixbuf.Pixbuf.new_from_file(
167
os.path.join(softwarecenter.paths.datadir,
168
"ui/gtk3/art/circle-dropshadow.png"))
169
self.set_focus_on_click(False)
170
self.set_name("exhibit-button")
171
self._dropshadow = None
172
# is the current active "page"
173
self.is_active = False
174
self.connect("size-allocate", self.on_size_allocate)
176
def on_size_allocate(self, *args):
177
a = self.get_allocation()
178
if (self._dropshadow is not None and
179
a.width == self._dropshadow.get_width() and
180
a.height == self._dropshadow.get_height()):
183
self._dropshadow = self.DROPSHADOW.scale_simple(
184
a.width, a.width, GdkPixbuf.InterpType.BILINEAR)
185
self._margin = int(float(a.width) / self.DROPSHADOW.get_width() * 15)
188
def do_draw(self, cr):
189
a = self.get_allocation()
190
#state = self.get_state_flags()
191
context = self.get_style_context()
193
ds_h = self._dropshadow.get_height()
194
y = (a.height - ds_h) / 2
195
Gdk.cairo_set_source_pixbuf(cr, self._dropshadow, 0, y)
197
Circle.layout(cr, self._margin, (a.height-ds_h)/2 + self._margin,
198
a.width-2*self._margin,
199
a.width-2*self._margin)
201
color = context.get_background_color(Gtk.StateFlags.SELECTED)
203
color = context.get_background_color(Gtk.StateFlags.INSENSITIVE)
205
Gdk.cairo_set_source_rgba(cr, color)
209
self.propagate_draw(child, cr)
213
class ExhibitArrowButton(ExhibitButton):
215
def __init__(self, arrow_type, shadow_type=Gtk.ShadowType.IN):
216
ExhibitButton.__init__(self)
218
a.set_padding(1,1,1,1)
219
a.add(Gtk.Arrow.new(arrow_type, shadow_type))
224
class ExhibitBanner(Gtk.EventBox):
227
"show-exhibits-clicked" : (GObject.SignalFlags.RUN_LAST,
229
(GObject.TYPE_PYOBJECT,),
233
DROPSHADOW_HEIGHT = 11
234
MAX_HEIGHT = 200 # pixels
38
238
Gtk.EventBox.__init__(self)
39
self.fixed = Gtk.Fixed()
40
self.image = Gtk.Image()
41
self.label = Gtk.Label()
42
self.downloader = SimpleFileDownloader()
43
self.downloader.connect(
44
"file-download-complete", self._on_file_download_complete)
47
self.set_right_pixel_cutoff(right_pixel_cutoff)
240
vbox.set_border_width(StockEms.SMALL)
243
# defined to make overriding softwarecenter.paths.datadir possible
244
self.NORTHERN_DROPSHADOW = os.path.join(
245
softwarecenter.paths.datadir,
246
"ui/gtk3/art/exhibit-dropshadow-n.png")
247
self.SOUTHERN_DROPSHADOW = os.path.join(
248
softwarecenter.paths.datadir,
249
"ui/gtk3/art/exhibit-dropshadow-s.png")
250
self.FALLBACK = os.path.join(
251
softwarecenter.paths.datadir,
252
"default_banner/fallback.png")
254
hbox = Gtk.HBox(spacing=StockEms.SMALL)
255
vbox.pack_end(hbox, False, False, 0)
257
next = ExhibitArrowButton(Gtk.ArrowType.RIGHT)
258
previous = ExhibitArrowButton(Gtk.ArrowType.LEFT)
259
self.nextprev_hbox = Gtk.HBox()
260
self.nextprev_hbox.pack_start(previous, False, False, 0)
261
self.nextprev_hbox.pack_start(next, False, False, 0)
262
hbox.pack_end(self.nextprev_hbox, False, False, 0)
264
self.index_hbox = Gtk.HBox(spacing=StockEms.SMALL)
265
alignment = Gtk.Alignment.new(1.0, 1.0, 0.0, 1.0)
266
alignment.add(self.index_hbox)
267
hbox.pack_end(alignment, False, False, 0)
274
self.old_image = None
275
self.renderer = _HtmlRenderer()
276
self.renderer.connect("render-finished", self.on_banner_rendered)
278
self.set_visible_window(False)
279
self.set_size_request(-1, self.MAX_HEIGHT)
282
next.connect('clicked', self.on_next_clicked)
283
previous.connect('clicked', self.on_previous_clicked)
287
self._cache_art_assets()
48
288
self._init_event_handling()
49
self._set_exhibit(exhibit)
50
self.connect("draw", self.on_draw)
52
def on_draw(self, widget, cr):
53
Gdk.cairo_set_source_rgba(cr, self.bgcolor)
57
def set_right_pixel_cutoff(self, right_pixel_cutoff):
58
self.right_pixel_cutoff=right_pixel_cutoff
60
def _set_exhibit(self, exhibit):
61
self.exhibit_data = exhibit
62
self.bgcolor = Gdk.RGBA()
63
self.bgcolor.parse(exhibit.background_color)
65
# - set background color
66
# background image first
67
self.downloader.download_file(exhibit.banner_url, use_cache=True)
68
self.fixed.put(self.image, 0, 0)
70
self.label.set_text(exhibit.title_translated)
72
self.label, exhibit.title_coords[0], exhibit.title_coords[1])
73
# FIXME: set font name, colors, size (that is not exposed in the API)
75
290
def _init_event_handling(self):
76
self.set_property("can-focus", True)
291
self.set_can_focus(True)
77
292
self.set_events(Gdk.EventMask.BUTTON_RELEASE_MASK|
78
293
Gdk.EventMask.ENTER_NOTIFY_MASK|
79
294
Gdk.EventMask.LEAVE_NOTIFY_MASK)
81
self.connect("enter-notify-event", self._on_enter_notify)
82
self.connect("leave-notify-event", self._on_leave_notify)
83
self.connect("button-release-event", self._on_button_release)
85
def _on_enter_notify(self, widget, event):
86
window = self.get_window()
88
window.set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND2))
90
def _on_leave_notify(self, widget, event):
91
window = self.get_window()
93
window.set_cursor(None)
95
def _on_button_release(self, widget, event):
295
#~ self.connect("enter-notify-event", self.on_enter_notify)
296
#~ self.connect("leave-notify-event", self.on_leave_notify)
297
self.connect("button-release-event", self.on_button_release)
299
def on_enter_notify(self, *args):
302
def on_leave_notify(self, *args):
305
def on_button_release(self, *args):
306
exhibit = self.exhibits[self.cursor]
307
if exhibit.package_names:
308
self.emit("show-exhibits-clicked", exhibit)
311
def on_next_clicked(self, *args):
316
def on_previous_clicked(self, *args):
321
def cleanup_timeout(self):
322
if self._timeout > 0:
323
GObject.source_remove(self._timeout)
327
def _render_exhibit_at_cursor(self):
328
# copy old image for the fade
330
self.old_image = self.image.copy()
332
self.renderer.set_exhibit(self.exhibits[self.cursor])
333
# make sure the active button is having a different color
334
for i, w in enumerate(self.index_hbox):
335
w.is_active = (i == self.cursor)
337
def on_paging_dot_clicked(self, dot, index):
339
self._render_exhibit_at_cursor()
341
# next() is a special function in py3 so we call this next_exhibit
342
def next_exhibit(self):
343
if len(self.exhibits)-1 == self.cursor:
347
self._render_exhibit_at_cursor()
352
self.cursor = len(self.exhibits)-1
355
self._render_exhibit_at_cursor()
358
def queue_next(self):
359
self.cleanup_timeout()
360
self._timeout = GObject.timeout_add_seconds(
361
self.TIMEOUT_SECONDS, self.next_exhibit)
364
def on_banner_rendered(self, renderer):
365
self.image = renderer.get_pixbuf()
367
if self.image.get_width() == 1:
368
# the offscreen window is not really as such content not
370
GObject.timeout_add(500, self.on_banner_rendered, renderer)
100
def _on_file_download_complete(self, downloader, path):
101
pb = GdkPixbuf.Pixbuf.new_from_file(path)
102
#~ pb = pb.scale_simple(600, 200, GdkPixbuf.InterpType.BILINEAR)
103
#~ print pb.get_width(), pb.get_height()
104
self.image.set_from_pixbuf(pb)
107
return "<Exhibit: '%s'>" % (self.exhibit_data.title_translated)
110
class ExhibitBanner(Gtk.Fixed):
113
"show-exhibits" : (GObject.SignalFlags.RUN_LAST,
115
(GObject.TYPE_PYOBJECT,),
120
Gtk.Fixed.__init__(self)
377
def _fade_in(self, step=0.05):
384
if self.alpha >= 1.0:
386
self.old_image = None
392
GObject.timeout_add(50, fade_step)
395
def _cache_art_assets(self):
397
assets = _asset_cache
401
#~ surf = cairo.ImageSurface.create_from_png(self.NORTHERN_DROPSHADOW)
402
#~ ptrn = cairo.SurfacePattern(surf)
403
#~ ptrn.set_extend(cairo.EXTEND_REPEAT)
404
#~ assets["n"] = ptrn
406
surf = cairo.ImageSurface.create_from_png(self.SOUTHERN_DROPSHADOW)
407
ptrn = cairo.SurfacePattern(surf)
408
ptrn.set_extend(cairo.EXTEND_REPEAT)
412
def do_draw(self, cr):
414
# hide the next/prev buttons if needed
415
if len(self.exhibits) == 1:
416
self.nextprev_hbox.hide()
418
self.nextprev_hbox.show()
420
# do the actual drawing
423
a = self.get_allocation()
425
cr.set_source_rgb(1,1,1)
428
if self.old_image is not None:
429
#x = (a.width - self.old_image.get_width()) / 2
432
Gdk.cairo_set_source_pixbuf(cr, self.old_image, x, y)
435
if self.image is not None:
436
#x = (a.width - self.image.get_width()) / 2
439
Gdk.cairo_set_source_pixbuf(cr, self.image, x, y)
440
cr.paint_with_alpha(self.alpha)
442
# paint dropshadows last
443
#~ cr.rectangle(0, 0, a.width, self.DROPSHADOW_HEIGHT)
445
#~ cr.set_source(assets["n"])
449
cr.rectangle(0, a.height-self.DROPSHADOW_HEIGHT,
450
a.width, self.DROPSHADOW_HEIGHT)
453
cr.translate(0, a.height-self.DROPSHADOW_HEIGHT)
454
cr.set_source(_asset_cache["s"])
459
cr.move_to(-0.5, a.height-0.5)
460
cr.rel_line_to(a.width+1, 0)
461
cr.set_source_rgba(1,1,1,0.75)
467
self.propagate_draw(child, cr)
123
470
def set_exhibits(self, exhibits_list):
124
for exhibit_data in exhibits_list:
125
exhibit = Exhibit(exhibit_data)
126
exhibit.connect("clicked", self._on_exhibit_clicked)
127
self.exhibits.append(exhibit)
128
# now draw them in the self.exhibits order
129
self._draw_exhibits()
131
def _draw_exhibits(self):
132
# remove the old ones
133
self.foreach(lambda w,d: self.remove(w), None)
136
for (i, exhibit) in enumerate(reversed(self.exhibits)):
137
# FIXME: we may need to put this to the right actually, in the spec
138
# the wireframe has it on the left, the mockup on the righ
139
self.put(exhibit, i*20, 0)
141
def _on_exhibit_clicked(self, exhibit):
142
if self.exhibits[0] == exhibit:
143
self.emit("show-exhibits",
144
exhibit.exhibit_data.package_names.split(","))
146
# exchange top with the clicked one
147
self.exhibits[self.exhibits.index(exhibit)] = self.exhibits[0]
148
self.exhibits[0] = exhibit
149
self._draw_exhibits()
152
if __name__ == "__main__":
471
self.exhibits = exhibits_list
474
for child in self.index_hbox:
477
for sigid in self._dotsigs:
478
GObject.source_remove(sigid)
481
for i, exhibit in enumerate(self.exhibits):
482
dot = ExhibitButton()
483
dot.set_size_request(StockEms.LARGE, StockEms.LARGE)
484
self._dotsigs.append(
485
dot.connect("clicked",
486
self.on_paging_dot_clicked,
487
len(self.exhibits) - 1 - i) # index
489
self.index_hbox.pack_end(dot, False, False, 0)
490
self.index_hbox.show_all()
492
self._render_exhibit_at_cursor()
495
def get_test_exhibits_window():
153
496
from mock import Mock
155
498
win = Gtk.Window()
156
win.set_size_request(600, 400)
499
win.set_size_request(600, 200)
501
exhibit_banner = ExhibitBanner()
503
exhibits_list = [DefaultExhibit()]
160
504
for (i, (title, url)) in enumerate([
161
505
("1 some title", "https://wiki.ubuntu.com/Brand?action=AttachFile&do=get&target=orangeubuntulogo.png"),
162
506
("2 another title", "https://wiki.ubuntu.com/Brand?action=AttachFile&do=get&target=blackeubuntulogo.png"),
163
507
("3 yet another title", "https://wiki.ubuntu.com/Brand?action=AttachFile&do=get&target=xubuntu.png"),
166
exhibit.background_color = "#000000"
167
exhibit.banner_url = url
168
exhibit.date_created = "2011-07-20 08:49:15"
169
exhibit.font_color = "#000000"
170
exhibit.font_name = ""
171
exhibit.font_size = 24
173
511
exhibit.package_names = "apt,2vcard"
174
512
exhibit.published = True
175
exhibit.title_coords = [10, 10]
513
exhibit.style = "some uri to html"
176
514
exhibit.title_translated = title
515
exhibit.banner_url = url
177
517
exhibits_list.append(exhibit)
179
exhibit_banner = ExhibitBanner()
180
519
exhibit_banner.set_exhibits(exhibits_list)
181
win.add(exhibit_banner)
521
scroll = Gtk.ScrolledWindow()
522
scroll.add_with_viewport(exhibit_banner)
184
526
win.connect("destroy", Gtk.main_quit)
529
if __name__ == "__main__":
530
softwarecenter.paths.datadir = "./data"
531
win = get_test_exhibits_window()