#!/usr/bin/python
import os
from gi.repository import Gtk, Gdk, GObject, Pango
from gi.repository import UbiquityWebcam, GdkPixbuf
from ubiquity import misc
def refresh():
while Gtk.events_pending():
Gtk.main_iteration()
def draw_round_rect(c, r, x, y, w, h):
c.move_to(x+r,y)
c.line_to(x+w-r,y); c.curve_to(x+w,y,x+w,y,x+w,y+r)
c.line_to(x+w,y+h-r); c.curve_to(x+w,y+h,x+w,y+h,x+w-r,y+h)
c.line_to(x+r,y+h); c.curve_to(x,y+h,x,y+h,x,y+h-r)
c.line_to(x,y+r); c.curve_to(x,y,x,y,x+r,y)
c.close_path()
def gtk_to_cairo_color(c):
color = Gdk.color_parse(c)
s = 1.0/65535.0
r = color.red * s
g = color.green * s
b = color.blue * s
return r, g, b
class StylizedFrame(Gtk.Alignment):
__gtype_name__ = 'StylizedFrame'
__gproperties__ = {
'radius': (GObject.TYPE_INT, 'Radius',
'The radius of the rounded corners.', 0,
GObject.constants.G_MAXINT, 10, GObject.PARAM_READWRITE),
'width' : (GObject.TYPE_INT, 'Width', 'The width of the outline.',
0, GObject.constants.G_MAXINT, 1,
GObject.PARAM_READWRITE),
}
def __init__(self):
Gtk.Alignment.__init__(self)
self.radius = 10
self.width = 1
def do_get_property(self, prop):
if prop.name in ('radius', 'width'):
return getattr(self, prop.name)
else:
return Gtk.Alignment.do_get_property(self, prop)
def do_set_property(self, prop, value):
if prop.name in ('radius', 'width'):
setattr(self, prop.name, value)
self.queue_draw()
else:
Gtk.Alignment.do_set_property(self, prop, value)
def paint_background(self, c):
c.set_source_rgb(*gtk_to_cairo_color('#fbfbfb'))
alloc = self.get_allocation()
draw_round_rect(c, self.radius,
self.width / 2, self.width / 2,
alloc.width - self.width,
alloc.height - self.width)
c.fill_preserve()
def do_draw(self, c):
# Background
self.paint_background(c)
# Edge
c.set_source_rgb(*gtk_to_cairo_color('#c7c7c6'))
c.set_line_width(self.width)
c.stroke()
if self.get_child():
top, bottom, left, right = self.get_padding()
c.translate(left, top)
self.get_child().draw(c)
GObject.type_register(StylizedFrame)
class ResizeWidget(Gtk.HPaned):
__gtype_name__ = 'ResizeWidget'
__gproperties__ = {
'part_size' : (GObject.TYPE_UINT64, 'Partition size',
'The size of the partition being resized', 1,
GObject.constants.G_MAXUINT64, 100,
GObject.PARAM_READWRITE),
'min_size' : (GObject.TYPE_UINT64, 'Minimum size',
'The minimum size that the existing partition can ' \
'be resized to', 0, GObject.constants.G_MAXUINT64, 0,
GObject.PARAM_READWRITE),
'max_size' : (GObject.TYPE_UINT64, 'Maximum size',
'The maximum size that the existing partition can ' \
'be resized to', 1, GObject.constants.G_MAXUINT64,
100, GObject.PARAM_READWRITE)
}
def do_get_property(self, prop):
return getattr(self, prop.name.replace('-', '_'))
def do_set_property(self, prop, value):
setattr(self, prop.name.replace('-', '_'), value)
def __init__(self, part_size=100, min_size=0, max_size=100,
existing_part=None, new_part=None):
Gtk.HPaned.__init__(self)
assert min_size <= max_size <= part_size
assert part_size > 0
# The size (b) of the existing partition.
self.part_size = part_size
# The min size (b) that the existing partition can be resized to.
self.min_size = min_size
# The max size (b) that the existing partition can be resized to.
self.max_size = max_size
# Use test window to figure out highlight color
test_window = Gtk.Window()
test_label = Gtk.Label()
test_window.add(test_label)
style = test_label.get_style_context()
self.highlight_color = style.get_background_color(
Gtk.StateFlags.SELECTED)
self.highlight_color.alpha = 0.5
# more recent python-gi has .to_color() override, copy it...
self.highlight_color_rgb = Gdk.Color(
self.highlight_color.red*65535,
self.highlight_color.green*65535,
self.highlight_color.blue*65535)
# FIXME: Why do we still need these event boxes to get proper bounds
# for the linear gradient?
self.existing_part = existing_part or PartitionBox()
eb = Gtk.EventBox()
eb.modify_bg(Gtk.StateFlags.NORMAL, self.highlight_color_rgb)
eb.add(self.existing_part)
self.pack1(eb, resize=False, shrink=False)
self.new_part = new_part or PartitionBox()
eb = Gtk.EventBox()
eb.modify_bg(Gtk.StateFlags.NORMAL, self.highlight_color_rgb)
eb.add(self.new_part)
self.pack2(eb, resize=False, shrink=False)
self.show_all()
# FIXME hideous, but do_realize fails inexplicably.
self.connect('realize', self.realize)
def realize(self, w):
# TEST: Make sure the value of the minimum size and maximum size
# equal the value of the widget when pushed to the min/max.
total = (self.new_part.get_allocation().width +
self.existing_part.get_allocation().width)
tmp = float(self.min_size) / self.part_size
pixels = int(tmp * total)
self.existing_part.set_size_request(pixels, -1)
tmp = ((float(self.part_size) - self.max_size) / self.part_size)
pixels = int(tmp * total)
self.new_part.set_size_request(pixels, -1)
def do_draw(self, cr):
s1 = self.existing_part.get_allocation().width
s2 = self.new_part.get_allocation().width
total = s1 + s2
percent = (float(s1) / float(total))
self.existing_part.set_size(percent * self.part_size)
percent = (float(s2) / float(total))
self.new_part.set_size(percent * self.part_size)
def set_pref_size(self, size):
s1 = self.existing_part.get_allocation().width
s2 = self.new_part.get_allocation().width
total = s1 + s2
percent = (float(size) / float(self.part_size))
val = percent * total
self.set_position(int(val))
def get_size(self):
'''Returns the size of the old partition,
clipped to the minimum and maximum sizes.
'''
s1 = self.existing_part.get_allocation().width
s2 = self.new_part.get_allocation().width
totalwidth = s1 + s2
size = int(float(s1) * self.part_size / float(totalwidth))
if size < self.min_size:
return self.min_size
elif size > self.max_size:
return self.max_size
else:
return size
GObject.type_register(ResizeWidget)
class DiskBox(Gtk.Box):
__gtype_name__ = 'DiskBox'
def add(self, partition, size):
Gtk.Box.add(self, partition, expand=False)
partition.set_size_request(size, -1)
def clear(self):
self.forall(lambda x: self.remove(x))
GObject.type_register(DiskBox)
class PartitionBox(Gtk.Alignment):
__gtype_name__ = 'PartitionBox'
__gproperties__ = {
'title' : (GObject.TYPE_STRING, 'Title', None, 'Title',
GObject.PARAM_READWRITE),
'icon-name' : (GObject.TYPE_STRING, 'Icon Name', None,
'distributor-logo', GObject.PARAM_READWRITE),
'extra' : (GObject.TYPE_STRING, 'Extra Text', None, '',
GObject.PARAM_READWRITE),
}
def do_get_property(self, prop):
if prop.name == 'title':
return self.ostitle.get_text()
elif prop.name == 'icon-name':
return self.logo.get_icon_name()
elif prop.name == 'extra':
return self.extra.get_text()
return getattr(self, prop.name)
def do_set_property(self, prop, value):
if prop.name == 'title':
self.ostitle.set_markup('%s' % value)
return
elif prop.name == 'icon-name':
self.logo.set_from_icon_name(value, Gtk.IconSize.DIALOG)
return
elif prop.name == 'extra':
self.extra.set_markup('%s' %
(value and value or ' '))
return
setattr(self, prop.name, value)
def __init__(self, title='', extra='', icon_name='distributor-logo'):
# 10 px above the topmost element
# 6 px between the icon and the title
# 4 px between the title and the extra heading
# 5 px between the extra heading and the size
# 12 px below the bottom-most element
Gtk.Alignment.__init__(self)
vbox = Gtk.Box()
vbox.set_orientation(Gtk.Orientation.VERTICAL)
self.logo = Gtk.Image.new_from_icon_name(icon_name,
Gtk.IconSize.DIALOG)
align = Gtk.Alignment.new(0.5, 0.5, 0.5, 0.5)
align.set_padding(10, 0, 0, 0)
align.add(self.logo)
vbox.pack_start(align, False, True, 0)
self.ostitle = Gtk.Label()
self.ostitle.set_ellipsize(Pango.EllipsizeMode.END)
align = Gtk.Alignment.new(0.5, 0.5, 0.5, 0.5)
align.set_padding(6, 0, 0, 0)
align.add(self.ostitle)
vbox.pack_start(align, False, True, 0)
self.extra = Gtk.Label()
self.extra.set_ellipsize(Pango.EllipsizeMode.END)
align = Gtk.Alignment.new(0.5, 0.5, 0.5, 0.5)
align.set_padding(4, 0, 0, 0)
align.add(self.extra)
vbox.pack_start(align, False, True, 0)
self.size = Gtk.Label()
self.size.set_ellipsize(Pango.EllipsizeMode.END)
align = Gtk.Alignment.new(0.5, 0.5, 0.5, 0.5)
align.set_padding(5, 12, 0, 0)
align.add(self.size)
vbox.pack_start(align, False, True, 0)
self.add(vbox)
self.ostitle.set_markup('%s' % title)
# Take up the space that would otherwise be used to create symmetry.
self.extra.set_markup('%s' % extra and extra or ' ')
self.show_all()
def set_size(self, size):
size = misc.format_size(size)
self.size.set_markup('%s' % size)
GObject.type_register(PartitionBox)
class StateBox(StylizedFrame):
__gtype_name__ = 'StateBox'
__gproperties__ = {
'label' : (GObject.TYPE_STRING, 'Label', None, 'label',
GObject.PARAM_READWRITE),
}
def do_get_property(self, prop):
if prop.name == 'label':
return self.label.get_text()
return getattr(self, prop.name)
def do_set_property(self, prop, value):
if prop.name == 'label':
self.label.set_text(value)
return
setattr(self, prop.name, value)
def __init__(self, text=''):
StylizedFrame.__init__(self)
alignment = Gtk.Alignment()
alignment.set_padding(7, 7, 15, 15)
hbox = Gtk.Box()
hbox.set_spacing(10)
self.image = Gtk.Image()
self.image.set_from_stock(Gtk.STOCK_YES, Gtk.IconSize.LARGE_TOOLBAR)
self.label = Gtk.Label(label=text)
self.label.set_alignment(0, 0.5)
hbox.pack_start(self.image, False, True, 0)
hbox.pack_start(self.label, True, True, 0)
alignment.add(hbox)
self.add(alignment)
self.show_all()
self.status = True
def set_state(self, state):
self.status = state
if state:
self.image.set_from_stock(Gtk.STOCK_YES,
Gtk.IconSize.LARGE_TOOLBAR)
else:
self.image.set_from_stock(Gtk.STOCK_NO,
Gtk.IconSize.LARGE_TOOLBAR)
def get_state(self):
return self.status
GObject.type_register(StateBox)
FACES_PATH = '/usr/share/pixmaps/faces'
class FaceSelector(Gtk.Box):
__gtype_name__ = 'FaceSelector'
def __init__(self, controller):
Gtk.Box.__init__(self)
self.set_orientation(Gtk.Orientation.VERTICAL)
self.set_homogeneous(False)
self.set_spacing(12)
self.controller = controller
vb_left = Gtk.Box(False, 3)
vb_left.set_orientation(Gtk.Orientation.VERTICAL)
self.photo_label = Gtk.Label('Take a photo:')
vb_left.pack_start(self.photo_label, False, False, 0)
f = Gtk.Frame()
self.webcam = UbiquityWebcam.Webcam()
self.webcam.connect('image-captured', self.image_captured)
f.add(self.webcam)
vb_left.pack_start(f, True, True, 0)
vb_right = Gtk.Box(False, 3)
vb_right.set_orientation(Gtk.Orientation.VERTICAL)
self.existing_label = Gtk.Label('Or choose an existing picture:')
vb_right.pack_start(self.existing_label, False, False, 0)
iv = Gtk.IconView()
iv.connect('selection-changed', self.selection_changed)
# TODO cjwatson 2012-03-21: Gtk.IconView should work this out
# itself, but I think that depends on having correct
# height-for-width geometry management everywhere, and we don't yet.
# See LP #961025.
iv.set_columns(2)
sw = Gtk.ScrolledWindow()
sw.set_shadow_type(Gtk.ShadowType.IN)
sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
sw.add(iv)
vb_right.pack_start(sw, True, True, 0)
hb = Gtk.Box(True, 30)
hb.pack_start(vb_left, True, True, 0)
hb.pack_start(vb_right, True, True, 0)
self.pack_start(hb, True, True, 0)
self.selected_image = Gtk.Image()
self.selected_image.set_size_request(96, 96)
self.pack_start(self.selected_image, True, True, 0)
m = Gtk.ListStore(GObject.type_from_name('GdkPixbuf'))
iv.set_model(m)
iv.set_pixbuf_column(0)
if os.path.exists(FACES_PATH):
for path in sorted(os.listdir(FACES_PATH)):
pb = GdkPixbuf.Pixbuf.new_from_file(
os.path.join(FACES_PATH, path))
m.append([pb])
def translate(self, lang):
self.photo_label.set_text(
self.controller.get_string('webcam_photo_label', lang))
self.existing_label.set_text(
self.controller.get_string('webcam_existing_label', lang))
self.webcam.get_property('take-button').set_label(
self.controller.get_string('webcam_take_button', lang))
def webcam_play(self):
self.webcam.play()
def webcam_stop(self):
self.webcam.stop()
def save_to(self, path):
pb = self.selected_image.get_pixbuf()
if not pb:
return False
d = os.path.dirname(path)
with misc.raised_privileges():
if not os.path.exists(d):
os.makedirs(d)
pb.savev(path, 'png', [], [])
def image_captured(self, unused, path):
pb = GdkPixbuf.Pixbuf.new_from_file_at_size(path, 96, 96);
self.selected_image.set_from_pixbuf(pb)
def selection_changed(self, iv):
selection = iv.get_selected_items()
if not selection:
return
selection = selection[0]
m = iv.get_model()
self.selected_image.set_from_pixbuf(m[selection][0])
GObject.type_register(FaceSelector)