From 1f91f7f7afd34143721ac66936546e4444950369 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 25 Feb 2007 17:53:10 -0500 Subject: [PATCH] New intro/setup screen --- configure.ac | 1 + shell/Makefile.am | 2 +- shell/intro/Makefile.am | 6 + shell/intro/__init__.py | 0 shell/intro/colorpicker.py | 214 ++++++++++++++++++++++++++++++++ shell/intro/glive.py | 186 ++++++++++++++++++++++++++++ shell/intro/intro.py | 241 +++++++++++++++++++++++++++++++++++++ shell/sugar-shell | 12 +- sugar/profile.py | 29 ++++- 9 files changed, 682 insertions(+), 9 deletions(-) create mode 100644 shell/intro/Makefile.am create mode 100644 shell/intro/__init__.py create mode 100644 shell/intro/colorpicker.py create mode 100644 shell/intro/glive.py create mode 100644 shell/intro/intro.py diff --git a/configure.ac b/configure.ac index a6c65ab5..aca65511 100644 --- a/configure.ac +++ b/configure.ac @@ -116,6 +116,7 @@ services/nm/Makefile services/clipboard/Makefile services/datastore/Makefile shell/Makefile +shell/intro/Makefile shell/data/Makefile shell/view/Makefile shell/view/devices/Makefile diff --git a/shell/Makefile.am b/shell/Makefile.am index 6d6f6336..2164436a 100644 --- a/shell/Makefile.am +++ b/shell/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = data model view +SUBDIRS = data model view intro bin_SCRIPTS = \ sugar-activity \ diff --git a/shell/intro/Makefile.am b/shell/intro/Makefile.am new file mode 100644 index 00000000..cc11aa90 --- /dev/null +++ b/shell/intro/Makefile.am @@ -0,0 +1,6 @@ +sugardir = $(pkgdatadir)/shell/intro +sugar_PYTHON = \ + __init__.py \ + colorpicker.py \ + intro.py \ + glive.py diff --git a/shell/intro/__init__.py b/shell/intro/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/shell/intro/colorpicker.py b/shell/intro/colorpicker.py new file mode 100644 index 00000000..104c9dfe --- /dev/null +++ b/shell/intro/colorpicker.py @@ -0,0 +1,214 @@ +# Copyright (C) 2007, Red Hat, Inc. +# +# 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; either version 2 of the License, or +# (at your option) any later version. +# +# 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 St, Fifth Floor, Boston, MA 02110-1301 USA + +import hippo +import random, math +import gobject + +from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics import color +from sugar.graphics import units +from sugar.graphics.xocolor import XoColor + +# Ported from a JavaScript implementation (C) 2007 Jacob Rus +# http://www.hcs.harvard.edu/~jrus/olpc/colorpicker.svg + + +# An array of arrays with value 1-9 for each of 40 different hues, +# starting with 5R, and going all the way around to 2.5R. + +munsell_colors = [ + # 1 2 3 4 5 6 7 8 9 + ["#40011d","#640d28","#8e172e","#bf1837","#f50141","#fc5f68","#f49599","#f1bdc3","#f8dfe9"], # 5 R + ["#410117","#630f1f","#8d1c21","#bd2024","#f21d22","#fc6252","#f5968d","#f2bdbe","#f9dfe7"], # 7.5 R + ["#410210","#631214","#940c00","#b03716","#d84b18","#f96735","#f49781","#f3bdb8","#fadfe5"], # 10 R + ["#410405","#611601","#7c3111","#a14614","#c85b15","#f07015","#fd9465","#f2beb2","#fbdfe1"], # 2.5 YR + ["#381001","#542305","#763601","#944f1f","#ba651f","#e07b1d","#f69955","#febc98","#fcdfdc"], # 5 YR + ["#2b180e","#492a14","#6b3e14","#8e5313","#b26a0a","#d1832b","#f69b26","#f9be91","#fbe0d7"], # 7.5 YR + ["#2a190b","#462c0f","#66400c","#885703","#a67020","#c9881a","#eba004","#fcbf6f","#f9e1d3"], # 10 YR + ["#271a09","#422e09","#614303","#7c5c1e","#9e7415","#bb8e32","#dda72c","#ffc01e","#f6e2d0"], # 2.5 Y + ["#251b09","#3f2f06","#574621","#775e19","#967709","#b2922a","#d2ac1d","#edc73f","#fae3b2"], # 5 Y + ["#221c0a","#3b3105","#534820","#706116","#8b7b2e","#a99525","#c8af13","#e3ca3a","#fde676"], # 7.5 Y + ["#1f1d0d","#363207","#4e4920","#6a6316","#857d2f","#a19825","#beb30e","#dacd39","#f8ea20"], # 10 Y + ["#1c1e11","#31340e","#474c02","#61651c","#7b810b","#959b2b","#b0b71b","#ccd23f","#e8ee25"], # 2.5 GY + ["#1a1e13","#2b3515","#3e4e0f","#576824","#6e841e","#85a007","#a1ba30","#b9d719","#d6f337"], # 5 GY + ["#082205","#18390a","#255301","#3e6d19","#508914","#61a704","#7ec22e","#90e01a","#abfc39"], # 7.5 GY + ["#01230d","#0b3a18","#0a541b","#2a6f2d","#048f1e","#0bad21","#15cb23","#0cea1c","#91fe81"], # 10 GY + ["#141f1a","#183728","#115336","#2b6d4c","#2e8b5b","#2ba96a","#20c877","#4be48e","#83feaf"], # 2.5 G + ["#131f1b","#15382c","#285041","#226d54","#138c68","#3ea681","#33c695","#54e2ae","#8afbcc"], # 5 G + ["#121f1c","#11382f","#245045","#176e5a","#3c8874","#33a78a","#1ac6a0","#46e2b9","#81fbd6"], # 7.5 G + ["#111f1e","#0d3832","#205049","#0a6e60","#38887a","#26a792","#4ec2ac","#38e2c4","#79fbe0"], # 10 G + ["#0f1f1f","#083836","#1c504d","#366a66","#338880","#13a79a","#45c2b4","#24e2ce","#72fbe9"], # 2.5 BG + ["#0e1f22","#03383b","#185053","#34696c","#2d8788","#49a3a2","#3cc2be","#5addd9","#6afbf4"], # 5 BG + ["#0c1f24","#00373e","#155057","#336970","#28878f","#46a2aa","#37c1c8","#55dde4","#92f5fb"], # 7.5 BG + ["#0c1f26","#23343b","#154f5c","#336975","#278697","#46a1b1","#34c0d2","#52dced","#b5effd"], # 10 BG + ["#0c1f27","#23343c","#154f60","#356879","#2b859d","#49a0b8","#3bbedb","#55dbf7","#d5e8f8"], # 2.5 B + ["#0c1f29","#023649","#1a4e64","#076988","#3584a2","#19a1ca","#49bce4","#88d4f5","#d8e7f9"], # 5 B + ["#0e1e2a","#0a354c","#224d66","#21688b","#0284b3","#3a9fce","#58baea","#90d2f9","#dbe6fa"], # 7.5 B + ["#101e2c","#13344d","#294b68","#2f668d","#2d82b6","#1f9edf","#43b9fe","#9bd0fd","#dee5fa"], # 10 B + ["#021e38","#1c334f","#1d4b77","#23649e","#277fc7","#159bf3","#7eb3f0","#b8cbee","#e2e4fa"], # 2.5 PB + ["#0d1c38","#14315d","#1a4885","#2661ac","#2e7cd6","#4f96f4","#8eb0f1","#bec9ef","#e4e4fa"], # 5 PB + ["#1c0560","#2c089c","#3b0ddf","#3f45f7","#566ff1","#7d8ef2","#a2abf2","#c7c7ee","#e8e3fa"], # 7.5 PB + ["#290652","#45018a","#5f0fbf","#7d16fe","#8261f3","#9784fb","#b0a4fe","#ccc3fe","#eae2f9"], # 10 PB + ["#2e0945","#510079","#7103ab","#911ddb","#a945fd","#b577fd","#c1a0f7","#d5c1fa","#ece1f9"], # 2.5 P + ["#340541","#54086b","#79079a","#9d18c8","#c226f9","#ca6bfc","#d397fc","#dcbff5","#ede1f8"], # 5 P + ["#37023f","#55115e","#7e0e8a","#a519b3","#d11ee0","#f045ff","#ee8af6","#f2b6f7","#f1e0f5"], # 7.5 P + ["#340a36","#5a0c59","#7f177a","#a9229e","#d822c6","#f749e2","#fa86e8","#fab4ee","#f2e0f3"], # 10 P + ["#360833","#600450","#880c6d","#b3128e","#e401b2","#fa4fc6","#fa8ad2","#f4b9de","#f4e0f2"], # 2.5 RP + ["#39062f","#5b1344","#8d0060","#b32078","#e71994","#fb56aa","#f68fbd","#f9b8d5","#f5dff0"], # 5 RP + ["#3b052b","#5e113e","#881951","#b81a69","#ec0c82","#f56099","#fa8eb3","#fcb7cf","#f6dfee"], # 7.5 RP + ["#3d0427","#600f38","#8c1645","#bd145a","#e72a6f","#f95f88","#fe8ea7","#ffb7c8","#f7dfed"], # 10 RP + ["#3f0222","#620d31","#8d153a","#bf124b","#e92a5e","#fb5f79","#f295a1","#efbdc8","#f6dfeb"], # 2.5 R +] + +# neutral values from 0 to 10. Not sure these are completely +# accurate; my method was a bit of a guesstimate. +munsell_neutrals = [ + '#000000', '#1d1d1d', '#323232', '#494949', '#626262', '#7c7c7c', + '#969696', '#b1b1b1', '#cbcbcb', '#e7e7e7', '#ffffff' +] + + +def _hex_color(hue, value): + # hue ranges from 0 (5R) to 39 (2.5R). value ranges from 1 to 9 + return munsell_colors[hue][value-1] + +def rand(n): + return int(math.floor(random.random() * n)) + + +class ColorPicker(hippo.CanvasBox, hippo.CanvasItem): + __gsignals__ = { + 'color': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), + } + + def __init__(self, **kwargs): + hippo.CanvasBox.__init__(self, **kwargs) + self.props.orientation = hippo.ORIENTATION_HORIZONTAL + + # 5YR7: #f69955 + # 5BP3: #1a4885 + self._fg_hue = 4 + self._fg_value = 7 + self._bg_hue = 28 + self._bg_value = 3 + + self._fg_hex = _hex_color(self._fg_hue, self._fg_value) + self._bg_hex = _hex_color(self._bg_hue, self._bg_value) + + self._pie_hues = 10 + self._slider_values = 9 + + self._xo = CanvasIcon(scale=units.XLARGE_ICON_SCALE, +# icon_name='stock-buddy.svg', + icon_name='theme:stock-buddy', + stroke_color=color.HTMLColor(self._fg_hex), + fill_color=color.HTMLColor(self._bg_hex)) + self._set_random_colors() + self._emit_color() + self._xo.connect('activated', self._xo_activated_cb) + self.append(self._xo) + + def _xo_activated_cb(self, item): + self._set_random_colors() + self._emit_color() + + def _emit_color(self): + xo_color = XoColor('%s,%s' % (self._xo.props.stroke_color.get_html(), + self._xo.props.fill_color.get_html())) + self.emit('color', xo_color) + + def _update_xo_hex(self, fg=None, bg=None): + """set the colors of the XO man""" + if fg: + self._xo.props.stroke_color = color.HTMLColor(fg) + if bg: + self._xo.props.fill_color = color.HTMLColor(bg) + + def _update_fg_hue(self, pie_fg_hue): + """change foreground (fill) hue""" + self._fg_hue = pie_fg_hue * (40 / self._pie_hues) + self._fg_hex = _hex_color(self._fg_hue, self._fg_value) + + self._update_xo_hex(fg=self._fg_hex) + + # set value slider + #for i in range(1, 10): + # setFill("fgv" + i, _hex_color(self._fg_hue, i)) + + # rotate selection dingus + #svgDocument.getElementById("fgHueSelect").setAttribute( + # "transform", + # "rotate(" + (360/self._pie_hues) * pie_fg_hue + ")" + #) + + def _update_bg_hue(self, pie_bg_hue): + """change background (stroke) hue""" + self._bg_hue = pie_bg_hue * (40 / self._pie_hues) + self._bg_hex = _hex_color(self._bg_hue, self._bg_value) + + self._update_xo_hex(bg=self._bg_hex) + + # set value slider + #for i in range(1, self._slider_values + 1): + # setFill("bgv" + i, _hex_color(self._bg_hue, i)) + + # rotate selection dingus + #svgDocument.getElementById("bgHueSelect").setAttribute( + # "transform", + # "rotate(" + (360/self._pie_hues) * pie_bg_hue + ")" + #) + + def _update_fg_value(self, slider_fg_value): + self._fg_value = slider_fg_value + self._fg_hex = _hex_color(self._fg_hue, self._fg_value) + + self._update_xo_hex(fg=self._fg_hex) + + # set hue pie + #for i in range(0, self._pie_hues): + # cur_hue = i * (40 / self._pie_hues) + # setFill("fgh" + i, _hex_color(cur_hue, self._fg_value)) + + # move selection dingus + #svgDocument.getElementById("fgValueSelect").setAttribute( + # "transform", + # "translate(0 -" + 22 * slider_fg_value + ")" + #) + + def _update_bg_value(self, slider_bg_value): + self._bg_value = slider_bg_value + self._bg_hex = _hex_color(self._bg_hue, self._bg_value) + + self._update_xo_hex(bg=self._bg_hex) + + # set hue pie + #for i in range(0, self._pie_hues): + # cur_hue = i * (40 / self._pie_hues) + # setFill("bgh" + i, _hex_color(cur_hue, self._bg_value)) + + # move selection dingus + #svgDocument.getElementById("bgValueSelect").setAttribute( + # "transform", + # "translate(0 -" + 22 * slider_bg_value + ")" + #) + + def _set_random_colors(self): + self._update_fg_hue(rand(self._pie_hues)) + self._update_fg_value(rand(self._slider_values)+1) + self._update_bg_hue(rand(self._pie_hues)) + self._update_bg_value(rand(self._slider_values)+1) + diff --git a/shell/intro/glive.py b/shell/intro/glive.py new file mode 100644 index 00000000..f02b261d --- /dev/null +++ b/shell/intro/glive.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 + +import gtk +import pygtk +pygtk.require('2.0') +import sys + +import pygst +pygst.require('0.10') +import gst +import gst.interfaces + +import gobject +gobject.threads_init() + +class Glive(gobject.GObject): + __gsignals__ = { + 'new-picture': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), + 'sink': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])) + } + + def __init__(self, parent, width, height): + self._parent = parent + + #check out the halfpipe, d00d. + self.pipeline = gst.Pipeline() + + self.v4l2src = gst.element_factory_make("v4l2src", "v4l2src") + self.t = gst.element_factory_make("tee", "tee") + self.t_src_pad = self.t.get_request_pad( "src%d" ) + self.vscale = gst.element_factory_make("videoscale", "videoscale") + self.ximagesink = gst.element_factory_make("ximagesink", "ximagesink") + + self.pipeline.add(self.v4l2src) + self.pipeline.add(self.t) + self.pipeline.add(self.vscale) + self.pipeline.add(self.ximagesink) + + self.v4l2src.link(self.t) + + videoscale_structure = gst.Structure("video/x-raw-rgb") + videoscale_structure['width'] = width + videoscale_structure['height'] = height + videoscale_structure['bpp'] = 16 + videoscale_structure['depth'] = 16 + videoscale_caps = gst.Caps(videoscale_structure) + self.t_src_pad.link(self.vscale.get_pad("sink")) + self.vscale.link(self.ximagesink, videoscale_caps) + #self.vscale.link(self.ximagesink) + + self.queue = gst.element_factory_make("queue", "queue") + self.queue.set_property("leaky", True) + self.queue.set_property("max-size-buffers", 1) + self.qsrc = self.queue.get_pad( "src" ) + self.qsink = self.queue.get_pad("sink") + self.ffmpeg = gst.element_factory_make("ffmpegcolorspace", "ffmpegcolorspace") + self.jpgenc = gst.element_factory_make("jpegenc", "jpegenc") + self.filesink = gst.element_factory_make("fakesink", "fakesink") + self.filesink.connect( "handoff", self.copyframe ) + self.filesink.set_property("signal-handoffs", True) + self.pipeline.add(self.queue, self.ffmpeg, self.jpgenc, self.filesink) + + #only link at snapshot time + #self.t.link(self.queue) + self.queue.link(self.ffmpeg) + self.ffmpeg.link(self.jpgenc) + self.jpgenc.link(self.filesink) + self.exposureOpen = False + + self._bus = self.pipeline.get_bus() + self._CONNECT_SYNC = -1 + self._CONNECT_MSG = -1 + self.doPostBusStuff() + + def copyframe(self, fsink, buffer, pad, user_data=None): + #for some reason, we get two back to back buffers, even though we + #ask for only one. + if (self.exposureOpen): + self.exposureOpen = False + piccy = gtk.gdk.pixbuf_loader_new_with_mime_type("image/jpeg") + piccy.write( buffer ) + piccy.close() + pixbuf = piccy.get_pixbuf() + del piccy + + self.t.unlink(self.queue) + self.queue.set_property("leaky", True) + + gobject.idle_add(self.loadPic, pixbuf) + + def loadPic( self, pixbuf ): + self.emit('new-picture', pixbuf) + + def takeSnapshot( self ): + if (self.exposureOpen): + return + else: + self.exposureOpen = True + self.t.link(self.queue) + + def doPostBusStuff(self): + self._bus.enable_sync_message_emission() + self._bus.add_signal_watch() + self._CONNECT_SYNC = self._bus.connect('sync-message::element', self.on_sync_message) + self._CONNECT_MSG = self._bus.connect('message', self.on_message) + + def on_sync_message(self, bus, message): + if message.structure is None: + return + if message.structure.get_name() == 'prepare-xwindow-id': + self.emit('sink', message.src) + message.src.set_property('force-aspect-ratio', True) + + def on_message(self, bus, message): + t = message.type + if (t == gst.MESSAGE_ERROR): + err, debug = message.parse_error() + if (self.on_eos): + self.on_eos() + self._playing = False + elif (t == gst.MESSAGE_EOS): + if (self.on_eos): + self.on_eos() + self._playing = False + + def on_eos( self ): + pass + + def stop(self): + self.pipeline.set_state(gst.STATE_NULL) + + def play(self): + self.pipeline.set_state(gst.STATE_PLAYING) + + def pause(self): + self.pipeline.set_state(gst.STATE_PAUSED) + + +class LiveVideoSlot(gtk.EventBox): + __gsignals__ = { + 'pixbuf': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), + } + + def __init__(self, width, height): + gtk.EventBox.__init__(self) + + self.imagesink = None + self.unset_flags(gtk.DOUBLE_BUFFERED) + self.connect('focus-in-event', self.focus_in) + self.connect('focus-out-event', self.focus_out) + self.connect("button-press-event", self.button_press) + + self.playa = Glive(self, width, height) + self.playa.connect('new-picture', self._new_picture_cb) + self.playa.connect('sink', self._new_sink_cb) + + def _new_picture_cb(self, playa, pixbuf): + self.emit('pixbuf', pixbuf) + + def _new_sink_sb(self, playa, sink): + if (self.imagesink != None): + assert self.window.xid + self.imagesink = None + del self.imagesink + self.imagesink = sink + self.imagesink.set_xwindow_id(self.window.xid) + + def focus_in(self, widget, event, args=None): + self.play() + + def focus_out(self, widget, event, args=None): + self.stop() + + def play( self ): + self.playa.play() + + def pause( self ): + self.playa.pause() + + def stop( self ): + self.playa.stop() + + def takeSnapshot( self ): + self.playa.takeSnapshot() diff --git a/shell/intro/intro.py b/shell/intro/intro.py new file mode 100644 index 00000000..34a83241 --- /dev/null +++ b/shell/intro/intro.py @@ -0,0 +1,241 @@ +# Copyright (C) 2007, Red Hat, Inc. +# +# 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; either version 2 of the License, or +# (at your option) any later version. +# +# 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 St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk, gobject +import hippo +import logging +from gettext import gettext as _ + +import os +from ConfigParser import ConfigParser + +from sugar import env + +from sugar.graphics import entry +from sugar.graphics import units +from sugar.graphics import font +from sugar.graphics import color +from sugar.graphics import iconbutton + +import colorpicker + +_VIDEO_WIDTH = 320 +_VIDEO_HEIGHT = 240 + + +class IntroFallbackVideo(gtk.EventBox): + __gtype_name__ = "IntroFallbackVideo" + + __gsignals__ = { + 'pixbuf': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), + } + + def __init__(self, **kwargs): + gtk.EventBox.__init__(self, **kwargs) + self._image = gtk.Image() + self._image.set_from_stock(gtk.STOCK_OPEN, -1) + self.add(self._image) + self.connect('button-press-event', self._button_press_cb) + + def _button_press_cb(self, widget, event): + filt = gtk.FileFilter() + filt.add_pixbuf_formats() + chooser = gtk.FileChooserDialog(_("Pick a buddy picture"), \ + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) + chooser.set_filter(filt) + resp = chooser.run() + if resp == gtk.RESPONSE_ACCEPT: + fname = chooser.get_filename() + pixbuf = gtk.gdk.pixbuf_new_from_file(fname) + (w, h) = self.get_size_request() + img_pixbuf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_BILINEAR) + self._image.set_from_pixbuf(img_pixbuf) + self.emit('pixbuf', pixbuf) + chooser.hide() + chooser.destroy() + return True + +class VideoBox(hippo.CanvasBox, hippo.CanvasItem): + __gtype_name__ = "SugarVideoBox" + + def __init__(self, **kwargs): + hippo.CanvasBox.__init__(self, **kwargs) + self.props.orientation = hippo.ORIENTATION_HORIZONTAL + self._pixbuf = None + + self._label = hippo.CanvasText(text=_("My Picture:"), + xalign=hippo.ALIGNMENT_START, + padding_right=units.grid_to_pixels(0.5)) + self._label.props.color = color.LABEL_TEXT.get_int() + self._label.props.font_desc = font.DEFAULT.get_pango_desc() + self.append(self._label) + + try: + import glive + self._video = glive.LiveVideoSlot(_VIDEO_WIDTH, _VIDEO_HEIGHT) + except ImportError: + self._video = IntroFallbackVideo() + + self._video.set_size_request(_VIDEO_WIDTH, _VIDEO_HEIGHT) + self._video.connect('pixbuf', self._new_pixbuf_cb) + + self._video_widget = hippo.CanvasWidget() + self._video_widget.props.widget = self._video + self.append(self._video_widget) + + def _new_pixbuf_cb(self, widget, pixbuf): + print "got new pixbuf" + if self._pixbuf: + del self._pixbuf + self._pixbuf = pixbuf + + def get_pixbuf(self): + return self._pixbuf + +class EntryBox(hippo.CanvasBox, hippo.CanvasItem): + __gtype_name__ = "SugarEntryBox" + + def __init__(self, **kwargs): + hippo.CanvasBox.__init__(self, **kwargs) + self.props.orientation = hippo.ORIENTATION_HORIZONTAL + + self._label = hippo.CanvasText(text=_("My Name:"), + xalign=hippo.ALIGNMENT_START, + padding_right=units.grid_to_pixels(0.5)) + self._label.props.color = color.LABEL_TEXT.get_int() + self._label.props.font_desc = font.DEFAULT.get_pango_desc() + self.append(self._label) + + self._entry = entry.Entry() + self.append(self._entry) + + def get_text(self): + return self._entry.props.text + + +class ColorBox(hippo.CanvasBox, hippo.CanvasItem): + __gtype_name__ = "SugarColorBox" + + def __init__(self, **kwargs): + hippo.CanvasBox.__init__(self, **kwargs) + self.props.orientation = hippo.ORIENTATION_HORIZONTAL + self._color = None + + self._label = hippo.CanvasText(text=_("My Color:"), + xalign=hippo.ALIGNMENT_START, + padding_right=units.grid_to_pixels(0.5)) + self._label.props.color = color.LABEL_TEXT.get_int() + self._label.props.font_desc = font.DEFAULT.get_pango_desc() + self.append(self._label) + + self._cp = colorpicker.ColorPicker() + self._cp.connect('color', self._new_color_cb) + self.append(self._cp) + + def _new_color_cb(self, widget, color): + self._color = color + + def get_color(self): + return self._color + +class IntroBox(hippo.CanvasBox, hippo.CanvasItem): + __gtype_name__ = 'SugarIntroBox' + + def __init__(self, **kwargs): + hippo.CanvasBox.__init__(self, **kwargs) + self._pixbuf = None + + self._video_box = VideoBox(xalign=hippo.ALIGNMENT_CENTER, + yalign=hippo.ALIGNMENT_START, + padding_bottom=units.grid_to_pixels(1)) + self.append(self._video_box) + + self._entry_box = EntryBox(xalign=hippo.ALIGNMENT_CENTER, + padding_bottom=units.grid_to_pixels(1)) + self.append(self._entry_box) + + self._color_box = ColorBox(xalign=hippo.ALIGNMENT_CENTER, + padding_bottom=units.grid_to_pixels(1)) + self.append(self._color_box) + + self._ok = iconbutton.IconButton(icon_name="theme:stock-forward", +# self._ok = iconbutton.IconButton(icon_name="stock-forward.svg", + padding_bottom=units.grid_to_pixels(0.5)) + self._ok.connect('activated', self._ok_activated) + self.append(self._ok) + + def _ok_activated(self, item): + pixbuf = self._video_box.get_pixbuf() + name = self._entry_box.get_text() + color = self._color_box.get_color() + + if not pixbuf or not name or not color: + print "pixbuf: %r, name %r, color %r" % (pixbuf, name, color) + return + + self._create_profile(pixbuf, name, color) + gtk.main_quit() + + def _create_profile(self, pixbuf, name, color): + # Save the buddy icon + icon_path = os.path.join(env.get_profile_path(), "buddy-icon.jpg") + scaled = pixbuf.scale_simple(200, 200, gtk.gdk.INTERP_BILINEAR) + pixbuf.save(icon_path, "jpeg", {"quality":"85"}) + + cp = ConfigParser() + section = 'Buddy' + cp.add_section(section) + cp.set(section, 'NickName', name) + cp.set(section, 'Color', color.to_string()) + + config_path = os.path.join(env.get_profile_path(), 'config') + f = open(config_path, 'w') + cp.write(f) + f.close() + + # Generate keypair + import commands + keypath = os.path.join(env.get_profile_path(), "owner.key") + cmd = "ssh-keygen -q -t dsa -f %s -C '' -N ''" % keypath + (s, o) = commands.getstatusoutput(cmd) + if s != 0: + logging.error("Could not generate key pair: %d" % s) + + +class IntroWindow(gtk.Window): + def __init__(self): + gtk.Window.__init__(self) + self.set_default_size(gtk.gdk.screen_width(), + gtk.gdk.screen_height()) + self.realize() + self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DESKTOP) + + self._canvas = hippo.Canvas() + self._intro_box = IntroBox(background_color=0x000000ff, + yalign=hippo.ALIGNMENT_START, + padding_top=units.grid_to_pixels(2), + padding_left=units.grid_to_pixels(3), + padding_right=units.grid_to_pixels(3)) + self._canvas.set_root(self._intro_box) + self.add(self._canvas) + self._canvas.show() + + +if __name__ == "__main__": + w = IntroWindow() + w.show_all() + w.connect('destroy', gtk.main_quit) + gtk.main() diff --git a/shell/sugar-shell b/shell/sugar-shell index 5b85da16..9632ced8 100755 --- a/shell/sugar-shell +++ b/shell/sugar-shell @@ -38,15 +38,17 @@ logger.start('shell') if len(sys.argv) == 1: sys.path.insert(0, os.path.join(env.get_data_dir(), 'shell')) -from view.FirstTimeDialog import FirstTimeDialog from view.Shell import Shell from model.ShellModel import ShellModel from shellservice import ShellService +from intro import intro -name = profile.get_nick_name() -if not name or not len(name): - dialog = FirstTimeDialog() - dialog.run() +# Do initial setup if needed +key = profile.get_pubkey() +if not key or not len(key): + win = intro.IntroWindow() + win.show_all() + gtk.main() profile.update() # Save our DBus Session Bus address somewhere it can be found diff --git a/sugar/profile.py b/sugar/profile.py index cb1309b2..43f3657c 100644 --- a/sugar/profile.py +++ b/sugar/profile.py @@ -15,6 +15,7 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import os +import logging from ConfigParser import ConfigParser from sugar import env @@ -41,11 +42,33 @@ class _Profile(object): if cp.has_option('Buddy', 'Color'): self.color = XoColor(cp.get('Buddy', 'Color')) - if cp.has_option('Buddy', 'PublicKey'): - self.pubkey = cp.get('Buddy', 'PublicKey') - del cp + self._load_pubkey() + + def _load_pubkey(self): + self.pubkey = None + + key_path = os.path.join(env.get_profile_path(), 'owner.key.pub') + try: + f = open(key_path, "r") + lines = f.readlines() + f.close() + except IOError, e: + logging.error("Error reading public key: %s" % e) + return + + magic = "ssh-dss " + for l in lines: + l = l.strip() + if not l.startswith(magic): + continue + self.pubkey = l[len(magic):] + break + if not self.pubkey: + logging.error("Error parsing public key.") + + def get_nick_name(): return _profile.name