3
from math import pi as PI
5
import softwarecenter.paths
7
from gi.repository import Gtk, Gdk
9
from buttons import MoreLink
10
from softwarecenter.ui.gtk3.em import StockEms
11
from softwarecenter.ui.gtk3.drawing import rounded_rect
14
class FlowableGrid(Gtk.Fixed):
18
def __init__(self, paint_grid_pattern=True):
19
Gtk.Fixed.__init__(self)
20
self.set_size_request(100, -1)
22
self.column_spacing = 0
25
self.paint_grid_pattern = paint_grid_pattern
26
self._cell_size = None
30
def _get_n_columns_for_width(self, width, cell_w, col_spacing):
31
n_cols = width / (cell_w + col_spacing)
34
def _layout_children(self, a):
35
if not self.get_visible(): return
37
children = self.get_children()
44
cell_w, cell_h = self.get_cell_size()
45
n_cols = self._get_n_columns_for_width(width, cell_w, col_spacing)
47
if n_cols == 0: return
48
cell_w = width / n_cols
49
self.n_columns = n_cols
51
#~ h_overhang = width - n_cols*cell_w - (n_cols-1)*col_spacing
53
#~ xo = h_overhang / (n_cols-1)
57
if len(children) % n_cols:
58
self.n_rows = len(children)/n_cols + 1
60
self.n_rows = len(children)/n_cols
63
for i, child in enumerate(children):
64
x = a.x + (i % n_cols) * (cell_w + col_spacing)
66
#~ x = a.x + (i % n_cols) * (cell_w + col_spacing + xo)
70
y = a.y + (i / n_cols) * (cell_h + row_spacing)
72
child_alloc = child.get_allocation()
75
child_alloc.width = cell_w
76
child_alloc.height = cell_h
77
child.size_allocate(child_alloc)
81
def do_get_request_mode(self):
82
return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH
84
def do_get_preferred_height_for_width(self, width):
85
old = self.get_allocation()
86
if width == old.width: old.height, old.height
88
cell_w, cell_h = self.get_cell_size()
89
n_cols = self._get_n_columns_for_width(
90
width, cell_w, self.column_spacing)
92
if not n_cols: return self.MIN_HEIGHT, self.MIN_HEIGHT
94
children = self.get_children()
95
n_rows = len(children) / n_cols
97
# store these for use when _layout_children gets called
98
if len(children) % n_cols:
101
pref_h = n_rows*cell_h + (n_rows-1)*self.row_spacing + 1
102
pref_h = max(self.MIN_HEIGHT, pref_h)
103
return pref_h, pref_h
106
def do_size_allocate(self, allocation):
107
self.set_allocation(allocation)
108
self._layout_children(allocation)
111
def do_draw(self, cr):
112
if not (self.n_columns and self.n_rows): return
114
if self.paint_grid_pattern:
117
for child in self: self.propagate_draw(child, cr)
121
def render_grid(self, cr):
122
context = self.get_style_context()
124
context.add_class("grid-lines")
125
bg = context.get_border_color(self.get_state_flags())
130
Gdk.cairo_set_source_rgba(cr, bg)
133
a = self.get_allocation()
134
w = a.width / self.n_columns
136
for i in range(self.n_columns):
137
cr.move_to(i*w+0.5, 0)
138
cr.rel_line_to(0, a.height-3)
141
w = a.height / self.n_rows
143
for i in range(self.n_rows):
144
cr.move_to(2, i*w+0.5)
145
cr.rel_line_to(a.width-4, 0)
151
def add_child(self, child):
152
self._cell_size = None
153
self.put(child, 0, 0)
156
def get_cell_size(self):
157
if self._cell_size is not None:
158
return self._cell_size
161
for child in self.get_children():
162
child_pref_w = child.get_preferred_width()[0]
163
child_pref_h = child.get_preferred_height()[0]
164
w = max(w, child_pref_w)
165
h = max(h, child_pref_h)
167
self._cell_size = (w, h)
170
def set_row_spacing(self, value):
171
self.row_spacing = value
174
def set_column_spacing(self, value):
175
self.column_spacing = value
176
self._layout_children(self.get_allocation())
179
def remove_all(self):
180
self._cell_size = None
185
# first tier of caching, cache component assets from which frames are
187
_frame_asset_cache = {}
188
class Frame(Gtk.Alignment):
191
ASSET_TAG = "default"
192
BORDER_IMAGE = os.path.join(
193
softwarecenter.paths.datadir, "ui/gtk3/art/frame-border-image.png")
194
#~ CORNER_LABEL = os.path.join(
195
#~ softwarecenter.paths.datadir, "ui/gtk3/art/corner-label.png")
197
def __init__(self, padding=0):
198
Gtk.Alignment.__init__(self)
199
# set padding + some additional padding in the bottom, left and
200
# right edges to factor in the dropshadow width, and ensure even
202
self.set_padding(padding, padding+2, padding+1, padding+1)
205
#~ self.show_corner_label = False
206
#~ self.layout = self.create_pango_layout("")
207
#~ self.layout.set_width(40960)
208
#~ self.layout.set_ellipsize(Pango.EllipsizeMode.END)
210
#~ assets = self._cache_art_assets()
211
self._cache_art_assets()
212
# second tier of caching, cache resultant surface of
213
# fully composed and rendered frame
214
self._frame_surface_cache = None
215
#~ self.connect_after("draw", self.on_draw_after,
216
#~ assets, self.layout)
217
self._allocation = Gdk.Rectangle()
218
self.connect("size-allocate", self.on_size_allocate)
219
self.connect("style-updated", self.on_style_updated)
222
def on_style_updated(self, widget):
223
self._frame_surface_cache = None
226
def on_size_allocate(self, *args):
227
old = self._allocation
228
cur = self.get_allocation()
229
if cur.width != old.width or cur.height != old.height:
230
self._frame_surface_cache = None
231
self._allocation = cur
235
def _cache_art_assets(self):
236
global _frame_asset_cache
238
assets = _frame_asset_cache
239
if at in assets: return assets
241
def cache_corner_surface(tag, xo, yo):
243
surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, sw, sh)
244
cr = cairo.Context(surf)
245
cr.set_source_surface(border_image, xo, yo)
251
def cache_edge_pattern(tag, xo, yo, sw, sh):
252
surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, sw, sh)
253
cr = cairo.Context(surf)
254
cr.set_source_surface(border_image, xo, yo)
256
ptrn = cairo.SurfacePattern(surf)
257
ptrn.set_extend(cairo.EXTEND_REPEAT)
262
# register the asset tag within the asset_cache
263
assets[at] = 'loaded'
266
border_image = cairo.ImageSurface.create_from_png(self.BORDER_IMAGE)
267
assets["corner-slice"] = cnr_slice = 10
268
w = border_image.get_width()
269
h = border_image.get_height()
272
# north-west corner of border image
273
cache_corner_surface("%s-nw" % at, 0, 0)
274
# northern edge pattern
275
cache_edge_pattern("%s-n" % at,
277
w-2*cnr_slice, cnr_slice)
279
cache_corner_surface("%s-ne" % at, -(w-cnr_slice), 0)
280
# eastern edge pattern
281
cache_edge_pattern("%s-e" % at,
282
-(w-cnr_slice), -cnr_slice,
283
cnr_slice, h-2*cnr_slice)
285
cache_corner_surface("%s-se" % at, -(w-cnr_slice), -(h-cnr_slice))
286
# southern edge pattern
287
cache_edge_pattern("%s-s" % at,
288
-cnr_slice, -(h-cnr_slice),
289
w-2*cnr_slice, cnr_slice)
291
cache_corner_surface("%s-sw" % at, 0, -(h-cnr_slice))
292
# western edge pattern
293
cache_edge_pattern("%s-w" % at, 0, -cnr_slice,
294
cnr_slice, h-2*cnr_slice)
298
def do_draw(self, cr):
304
def on_draw(self, cr):
305
a = self.get_allocation()
306
self.render_frame(cr, a, self.BORDER_RADIUS, _frame_asset_cache)
308
for child in self: self.propagate_draw(child, cr)
311
#~ def on_draw_after(self, widget, cr, assets, layout):
312
#~ if not self.show_corner_label: return
314
#~ surf = assets["corner-label"]
315
#~ w = surf.get_width()
316
#~ h = surf.get_height()
318
#~ # the following arbitrary adjustments are specific to the
319
#~ # corner-label.png image...
321
#~ # alter the to allow drawing outside of the widget bounds
322
#~ cr.rectangle(-10, -10, w+4, h+4)
324
#~ cr.set_source_surface(surf, -7, -8)
327
#~ ex = layout.get_pixel_extents()[1]
328
#~ # transalate to the visual center of the corner-label
329
#~ cr.translate(19, 18)
330
#~ # rotate counter-clockwise
331
#~ cr.rotate(-pi*0.25)
332
#~ # paint normal markup
333
#~ Gtk.render_layout(widget.get_style_context(),
334
#~ cr, -ex.width/2, -ex.height/2, layout)
338
def set_show_corner_label(self, show_label):
339
if (not self.layout.get_text() and
340
self.show_corner_label == show_label): return
341
global _frame_asset_cache
342
assets = _frame_asset_cache
344
if "corner-label" not in assets:
346
surf = cairo.ImageSurface.create_from_png(self.CORNER_LABEL)
347
assets["corner-label"] = surf
349
self.show_corner_label = show_label
353
#~ def set_corner_label(self, markup):
354
#~ markup = '<span font_desc="12" color="white"><b>%s</b></span>' % markup
355
#~ self.set_show_corner_label(True)
356
#~ self.layout.set_markup(markup, -1)
360
def render_frame(self, cr, a, border_radius, assets):
361
# we cache as much of the drawing as possible
362
# store a copy of the rendered frame surface, so we only have to
363
# do a full redraw if the widget dimensions change
364
if self._frame_surface_cache is None:
365
surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, a.width, a.height)
366
_cr = cairo.Context(surf)
372
cnr_slice = assets["corner-slice"]
374
# paint north-west corner
375
_cr.set_source_surface(assets["%s-nw" % at], 0, 0)
380
_cr.set_source(assets["%s-n" % at])
381
_cr.rectangle(cnr_slice, 0, width-2*cnr_slice, cnr_slice)
386
# paint north-east corner
387
_cr.set_source_surface(assets["%s-ne" % at],
393
_cr.translate(width-cnr_slice, cnr_slice)
394
_cr.set_source(assets["%s-e" % at])
395
_cr.rectangle(0, 0, cnr_slice, height-2*cnr_slice)
400
# paint south-east corner
401
_cr.set_source_surface(assets["%s-se" % at],
408
_cr.translate(cnr_slice, height-cnr_slice)
409
_cr.set_source(assets["%s-s" % at])
410
_cr.rectangle(0, 0, width-2*cnr_slice, cnr_slice)
415
# paint south-west corner
416
_cr.set_source_surface(assets["%s-sw" % at],
422
_cr.translate(0, cnr_slice)
423
_cr.set_source(assets["%s-w" % at])
424
_cr.rectangle(0, 0, cnr_slice, height-2*cnr_slice)
430
rounded_rect(_cr, 3, 2, a.width-6, a.height-6, border_radius)
431
context = self.get_style_context()
432
bg = context.get_background_color(self.get_state_flags())
434
Gdk.cairo_set_source_rgba(_cr, bg)
437
lin = cairo.LinearGradient(0, 0, 0, max(300, a.height))
438
lin.add_color_stop_rgba(0, 1, 1, 1, 0.02)
439
lin.add_color_stop_rgba(1, 0, 0, 0, 0.06)
443
self._frame_surface_cache = surf
446
# paint the cached surface and apply a rounded rect clip to
448
A = self.get_allocation()
449
xo, yo = a.x-A.x, a.y-A.y
451
cr.set_source_surface(self._frame_surface_cache, xo, yo)
454
#~ rounded_rect(cr, xo+3, yo+2, a.width-6, a.height-6, border_radius)
459
class SmallBorderRadiusFrame(Frame):
463
BORDER_IMAGE = os.path.join(
464
softwarecenter.paths.datadir, "ui/gtk3/art/frame-border-image-2px-border-radius.png")
466
def __init__(self, padding=3):
467
Frame.__init__(self, padding)
471
class FramedBox(Frame):
473
def __init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=0, padding=0):
474
Frame.__init__(self, padding)
475
self.box = Gtk.Box.new(orientation, spacing)
476
Gtk.Alignment.add(self, self.box)
479
def add(self, *args, **kwargs):
480
return self.box.add(*args, **kwargs)
482
def pack_start(self, *args, **kwargs):
483
return self.box.pack_start(*args, **kwargs)
485
def pack_end(self, *args, **kwargs):
486
return self.box.pack_end(*args, **kwargs)
489
class HeaderPosition:
495
class FramedHeaderBox(FramedBox):
499
def __init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=0, padding=0):
500
FramedBox.__init__(self, Gtk.Orientation.VERTICAL, spacing, padding)
501
self.header = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing)
502
self.header_alignment = Gtk.Alignment()
503
self.header_alignment.add(self.header)
504
self.box.pack_start(self.header_alignment, False, False, 0)
505
self.content_box = Gtk.Box.new(orientation, spacing)
506
self.box.add(self.content_box)
509
def on_draw(self, cr):
510
a = self.get_allocation()
511
self.render_frame(cr, a, Frame.BORDER_RADIUS, _frame_asset_cache)
512
a = self.header_alignment.get_allocation()
513
self.render_header(cr, a, Frame.BORDER_RADIUS, _frame_asset_cache)
515
for child in self: self.propagate_draw(child, cr)
518
def add(self, *args, **kwargs):
519
return self.content_box.add(*args, **kwargs)
521
def pack_start(self, *args, **kwargs):
522
return self.content_box.pack_start(*args, **kwargs)
524
def pack_end(self, *args, **kwargs):
525
return self.content_box.pack_end(*args, **kwargs)
527
# XXX: non-functional with current code...
528
#~ def set_header_expand(self, expand):
529
#~ alignment = self.header_alignment
534
#~ alignment.set(alignment.get_property("xalign"),
535
#~ alignment.get_property("yalign"),
538
def set_header_position(self, position):
539
alignment = self.header_alignment
540
alignment.set(position, 0.5,
541
alignment.get_property("xscale"),
542
alignment.get_property("yscale"))
544
def set_header_label(self, label):
545
if not hasattr(self, "title"):
546
self.title = Gtk.Label()
547
self.title.set_padding(StockEms.MEDIUM, StockEms.SMALL)
548
context = self.title.get_style_context()
549
context.add_class("frame-header-title")
550
self.header.pack_start(self.title, False, False, 0)
553
self.title.set_markup(self.MARKUP % label)
556
def header_implements_more_button(self, callback=None):
557
if not hasattr(self, "more"):
558
self.more = MoreLink()
559
self.header.pack_end(self.more, False, False, 0)
562
def render_header(self, cr, a, border_radius, assets):
564
if hasattr(self, "more"):
565
context = self.get_style_context()
567
# set the arrow fill color
568
context = self.more.get_style_context()
571
bg = context.get_background_color(self.get_state_flags())
572
Gdk.cairo_set_source_rgba(cr, bg)
574
# the arrow shape stuff
575
r = Frame.BORDER_RADIUS - 1
576
ta = self.more.get_allocation()
581
if self.get_direction() == Gtk.TextDirection.RTL:
583
w = ta.width + StockEms.MEDIUM
586
cr.arc(r+x, r+y, r, PI, 270*PI_OVER_180)
588
cr.line_to(x+w - StockEms.MEDIUM, y + h/2)
596
cr.line_to(x+w - StockEms.MEDIUM, y + h/2)
600
x = ta.x - a.x - StockEms.MEDIUM
601
w = ta.width + StockEms.MEDIUM - 1
604
cr.arc(x+w-r, y+r, r, 270*PI_OVER_180, 0)
607
cr.line_to(x + StockEms.MEDIUM, y + h/2)
613
cr.line_to(x + StockEms.MEDIUM, y + h/2)
616
bc = context.get_border_color(self.get_state_flags())
617
Gdk.cairo_set_source_rgba(cr, bc)
623
# paint the containers children
624
for child in self: self.propagate_draw(child, cr)
628
# this is used in the automatic tests
629
def get_test_container_window():
631
win.set_size_request(500, 300)
637
t = buttons.CategoryTile("test", "folder")
640
scroll = Gtk.ScrolledWindow()
641
scroll.add_with_viewport(f)
646
win.connect("destroy", lambda x: Gtk.main_quit())
649
if __name__ == '__main__':
650
win = get_test_container_window()