Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activity.py309
-rw-r--r--char.py19
-rw-r--r--document.py4
-rw-r--r--ground.py5
-rw-r--r--messenger.py284
-rw-r--r--montage.py256
-rw-r--r--screen.py58
-rw-r--r--shared.py130
-rw-r--r--sound.py9
-rw-r--r--toolbars.py104
10 files changed, 747 insertions, 431 deletions
diff --git a/activity.py b/activity.py
index b272763..c00f2b8 100644
--- a/activity.py
+++ b/activity.py
@@ -12,17 +12,12 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-from sugar.activity import activity
-from sugar.presence import presenceservice
-from sugar.presence.tubeconn import TubeConnection
-import telepathy
-import telepathy.client
-from dbus import Interface
-from dbus.service import method, signal
-from dbus.gobject_service import ExportedGObject
+import gtk
from gettext import gettext as _
-from sugar.activity.activity import get_activity_root
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.toggletoolbutton import ToggleToolButton
+from sugar.activity.activity import ActivityToolbox
import montage
import lessons
@@ -30,25 +25,21 @@ import document
import char
import ground
import sound
-from toolbars import *
+from shared import SharedActivity
+from messenger import Messenger, SERVICE
+from utils import *
-SERVICE = 'org.freedesktop.Telepathy.Tube.Connect'
-IFACE = SERVICE
-PATH = '/org/freedesktop/Telepathy/Tube/Connect'
-
-#TMPDIR = os.path.join(get_activity_root(), 'tmp')
-
-class CartoonBuilderActivity(activity.Activity):
+class CartoonBuilderActivity(SharedActivity):
def __init__(self, handle):
- activity.Activity.__init__(self,handle)
-
self.notebook = gtk.Notebook()
+ SharedActivity.__init__(self, self.notebook, SERVICE, handle)
+
+ self.connect('init', self._init_cb)
+ self.connect('tube', self._tube_cb)
+
self.notebook.show()
self.notebook.props.show_border = False
self.notebook.props.show_tabs = False
- # XXX do it after(possible) read_file() invoking
- # have to rely on calling read_file() from map_cb in sugar-toolkit
- self.notebook.connect_after('map', self._map_cb)
self.montage = montage.View()
self.notebook.append_page(self.montage)
@@ -56,7 +47,7 @@ class CartoonBuilderActivity(activity.Activity):
self.lessons.show()
self.notebook.append_page(self.lessons)
- toolbox = activity.ActivityToolbox(self)
+ toolbox = ActivityToolbox(self)
toolbox.show()
toolbox.connect('current-toolbar-changed', self._toolbar_changed_cb)
self.set_toolbox(toolbox)
@@ -70,35 +61,6 @@ class CartoonBuilderActivity(activity.Activity):
toolbox.add_toolbar(_('Lessons'), lessons_bar)
toolbox.set_current_toolbar(1)
- self.set_canvas(self.notebook)
-
- """
- # mesh stuff
- self.pservice = presenceservice.get_instance()
- owner = self.pservice.get_owner()
- self.owner = owner
- try:
- name, path = self.pservice.get_preferred_connection()
- self.tp_conn_name = name
- self.tp_conn_path = path
- self.conn = telepathy.client.Connection(name, path)
- except TypeError:
- pass
- self.initiating = None
-
- #sharing stuff
- self.game = None
- self.connect('shared', self._shared_cb)
- if self._shared_activity:
- # we are joining the activity
- self.connect('joined', self._joined_cb)
- if self.get_shared():
- # oh, OK, we've already joined
- self._joined_cb()
- else:
- # we are creating the activity
- pass
- """
def read_file(self, filepath):
document.load(filepath)
@@ -109,177 +71,94 @@ class CartoonBuilderActivity(activity.Activity):
def write_file(self, filepath):
document.save(filepath)
- def _map_cb(self, widget):
+ def _init_cb(self, widget):
self.montage.restore()
+ def _tube_cb(self, activity, tube_conn, initiating):
+ self.messenger = Messenger(tube_conn, initiating, self.montage)
+
def _toolbar_changed_cb(self, widget, index):
if index == 2:
self.notebook.set_current_page(1)
else:
self.notebook.set_current_page(0)
-
-
-
-
-
-
- def _shared_cb(self,activity):
- self.initiating = True
- self._setup()
- id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
- SERVICE, {})
- #self.app.export.set_label('Shared Me')
-
- def _joined_cb(self,activity):
- if self.game is not None:
- return
-
- if not self._shared_activity:
- return
-
- #for buddy in self._shared_activity.get_joined_buddies():
- # self.buddies_panel.add_watcher(buddy)
-
- #logger.debug('Joined an existing Connect game')
- #self.app.export.set_label('Joined You')
- self.initiating = False
- self._setup()
-
- #logger.debug('This is not my activity: waiting for a tube...')
- self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
- reply_handler=self._list_tubes_reply_cb,
- error_handler=self._list_tubes_error_cb)
-
- def _setup(self):
- if self._shared_activity is None:
+class MontageToolbar(gtk.Toolbar):
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ self.playButton = ToggleToolButton('media-playback-start')
+ self.playButton.connect('toggled', self._play_cb)
+ self.insert(self.playButton, -1)
+ self.playButton.set_tooltip(_('Play / Pause'))
+
+ # Play button Image
+ self.playButtonImg = gtk.Image()
+ self.playButtonImg.show()
+ self.playButtonImg.set_from_icon_name('media-playback-start', gtk.ICON_SIZE_LARGE_TOOLBAR)
+
+ # Pause button Image
+ self.pauseButtonImg = gtk.Image()
+ self.pauseButtonImg.show()
+ self.pauseButtonImg.set_from_icon_name('media-playback-pause', gtk.ICON_SIZE_LARGE_TOOLBAR)
+
+ tempo = TempoSlider(0, 10)
+ tempo.adjustment.connect("value-changed", self._tempo_cb)
+ tempo.set_size_request(250, -1)
+ tempo.set_value(5)
+ tempo_item = gtk.ToolItem()
+ tempo_item.add(tempo)
+ self.insert(tempo_item, -1)
+
+ separator = gtk.SeparatorToolItem()
+ self.insert(separator,-1)
+
+ clear_tape = ToolButton('sl-reset')
+ clear_tape.connect('clicked', self._clear_tape_cb)
+ clear_tape.set_tooltip(_(''))
+ self.insert(clear_tape, -1)
+
+ self.show_all()
+
+ def _clear_tape_cb(self, widget):
+ montage.clear_tape()
+
+ def _tempo_cb(self, widget):
+ montage.set_tempo(widget.value)
+
+ def _play_cb(self, widget):
+ if widget.get_active():
+ widget.set_icon_widget(self.pauseButtonImg)
+ sound.play()
+ montage.play()
+ else:
+ widget.set_icon_widget(self.playButtonImg)
+ sound.stop()
+ montage.stop()
+
+class LessonsToolbar(gtk.Toolbar):
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+ self._mask = False
+
+ for lesson in lessons.THEMES:
+ button = gtk.ToggleToolButton()
+ button.set_label(lesson.name)
+ button.connect('clicked', self._lessons_cb, lesson)
+ self.insert(button, -1)
+
+ self.get_nth_item(0).set_active(True)
+ self.show_all()
+
+ def _lessons_cb(self, widget, lesson):
+ if self._mask:
return
+ self._mask = True
- bus_name, conn_path, channel_paths = self._shared_activity.get_channels()
-
- # Work out what our room is called and whether we have Tubes already
- room = None
- tubes_chan = None
- text_chan = None
- for channel_path in channel_paths:
- channel = telepathy.client.Channel(bus_name, channel_path)
- htype, handle = channel.GetHandle()
- if htype == telepathy.HANDLE_TYPE_ROOM:
- #logger.debug('Found our room: it has handle#%d "%s"',
- # handle, self.conn.InspectHandles(htype, [handle])[0])
- room = handle
- ctype = channel.GetChannelType()
- if ctype == telepathy.CHANNEL_TYPE_TUBES:
- #logger.debug('Found our Tubes channel at %s', channel_path)
- tubes_chan = channel
- elif ctype == telepathy.CHANNEL_TYPE_TEXT:
- #logger.debug('Found our Text channel at %s', channel_path)
- text_chan = channel
-
- if room is None:
- #logger.error("Presence service didn't create a room")
- return
- if text_chan is None:
- #logger.error("Presence service didn't create a text channel")
- return
+ for i, j in enumerate(lessons.THEMES):
+ if j != lesson:
+ self.get_nth_item(i).set_active(False)
- # Make sure we have a Tubes channel - PS doesn't yet provide one
- if tubes_chan is None:
- #logger.debug("Didn't find our Tubes channel, requesting one...")
- tubes_chan = self.conn.request_channel(telepathy.CHANNEL_TYPE_TUBES,
- telepathy.HANDLE_TYPE_ROOM, room, True)
-
- self.tubes_chan = tubes_chan
- self.text_chan = text_chan
-
- tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube',
- self._new_tube_cb)
-
- def _list_tubes_reply_cb(self, tubes):
- for tube_info in tubes:
- self._new_tube_cb(*tube_info)
-
- def _list_tubes_error_cb(self, e):
- #logger.error('ListTubes() failed: %s', e)
- pass
-
- def _new_tube_cb(self, id, initiator, type, service, params, state):
- #logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
- # 'params=%r state=%d', id, initiator, type, service,
- # params, state)
-
- if (self.game is None and type == telepathy.TUBE_TYPE_DBUS and
- service == SERVICE):
- if state == telepathy.TUBE_STATE_LOCAL_PENDING:
- self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
-
- tube_conn = TubeConnection(self.conn,
- self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
- id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
- self.game = ConnectGame(tube_conn, self.initiating, self)
-
-class ConnectGame(ExportedGObject):
- def __init__(self,tube, is_initiator, activity):
- super(ConnectGame,self).__init__(tube,PATH)
- self.tube = tube
- self.is_initiator = is_initiator
- self.entered = False
- self.activity = activity
-
- self.ordered_bus_names=[]
- self.tube.watch_participants(self.participant_change_cb)
-
- def participant_change_cb(self, added, removed):
- if not self.entered:
- if self.is_initiator:
- self.add_hello_handler()
- else:
- self.Hello()
- self.entered = True
-
- @signal(dbus_interface=IFACE,signature='')
- def Hello(self):
- """Request that this player's Welcome method is called to bring it
- up to date with the game state.
- """
-
- @method(dbus_interface=IFACE, in_signature='s', out_signature='')
- def Welcome(self, sdata):
- #sdata is the zip file contents
- #self.activity.app.lessonplans.set_label('got data to restore')
- self.activity.app.restore(str(sdata))
-
- def add_hello_handler(self):
- self.tube.add_signal_receiver(self.hello_cb, 'Hello', IFACE,
- path=PATH, sender_keyword='sender')
-
- def hello_cb(self, sender=None):
- self.tube.get_object(sender, PATH).Welcome(self.activity.app.getsdata(),dbus_interface=IFACE)
-
-"""
- def getsdata(self):
- #self.lessonplans.set_label('getting sdata')
- # THE BELOW SHOULD WORK BUT DOESN'T
- #zf = StringIO.StringIO()
- #self.savetozip(zf)
- #zf.seek(0)
- #sdata = zf.read()
- #zf.close()
- # END OF STUFF THAT DOESN'T WORK
- sdd = {}
- tmpbgpath = os.path.join(TMPDIR,'back.png')
- self.bgpixbuf.save(tmpbgpath,'png')
- sdd['pngdata'] = file(tmpbgpath).read()
- os.remove(tmpbgpath)
- sdd['fgpixbufpaths'] = self.fgpixbufpaths
- #sdd['fgpixbufs'] = []
- #count = 1
- #for pixbuf in self.fgpixbufs:
- # filename = '%02d.png' % count
- # filepath = os.path.join(TMPDIR,filename)
- # pixbuf.save(filepath,'png')
- # sdd['fgpixbufs'].append(file(filepath).read())
- # os.remove(filepath)
- # count += 1
- return pickle.dumps(sdd)
-"""
+ widget.props.active = True
+ lesson.change()
+ self._mask = False
diff --git a/char.py b/char.py
index 7855161..c91c9b9 100644
--- a/char.py
+++ b/char.py
@@ -31,10 +31,11 @@ def load():
class Frame:
def __init__(self, id):
self.id = id
+ self.name = ''
self._thumb = None
self._orig = None
- def read(self):
+ def serialize(self):
if self._orig:
return pixbuf2str(self._orig)
else:
@@ -100,9 +101,11 @@ class CustomFrame(Frame):
def select(self):
if self._orig:
return True;
- self.id, self._orig = theme.choose(lambda jobject: (jobject.object_id,
- theme.pixbuf(jobject.file_path)), (None, None))
- if self.id:
+ self.name, self.id, self._orig = theme.choose(lambda jobject:
+ (jobject.metadata['title'], jobject.object_id,
+ theme.pixbuf(jobject.file_path)),
+ (None, None, None))
+ if self.name:
self._thumb = theme.scale(self._orig)
return True
else:
@@ -117,14 +120,16 @@ class Char:
for i in sorted(glob.glob(theme.path(dir, '*'))):
self.frames.append(PreinstalledFrame(
os.path.join(dir, os.path.basename(i))))
- for i in range(len(self.frames)-1,
- theme.FRAME_ROWS*theme.FRAME_COLS):
- self.frames.append(EmptyFrame())
self._thumb = theme.pixbuf(thumbfile, theme.THUMB_SIZE)
+ self._custom = False
else:
for i in range(0, theme.FRAME_ROWS*theme.FRAME_COLS):
self.frames.append(CustomFrame())
self._thumb = theme.CUSTOM_FRAME_THUMB
+ self._custom = True
+
+ def custom(self):
+ return self._custom
def thumb(self):
return self._thumb
diff --git a/document.py b/document.py
index 8d43895..9273f2c 100644
--- a/document.py
+++ b/document.py
@@ -47,7 +47,7 @@ def save(filepath):
if value.custom():
node['custom'] = True
node['filename'] = arcname
- zip.writestr(arcname, value.read())
+ zip.writestr(arcname, value.serialize())
else:
node['custom'] = False
node['name'] = unicode(value.name)
@@ -60,7 +60,7 @@ def save(filepath):
[i for i in set(Document.tape) if not i.empty() and i.custom()]):
arcname = 'frame%03d.png' % i
cfg['frames'][frame.id] = arcname
- zip.writestr(arcname, frame.read())
+ zip.writestr(arcname, frame.serialize())
for i, frame in enumerate(Document.tape):
if not frame.empty():
diff --git a/ground.py b/ground.py
index 4aef242..1f97514 100644
--- a/ground.py
+++ b/ground.py
@@ -22,7 +22,7 @@ def load():
from document import Document
if Document.ground and Document.ground.custom():
- THEMES.insert(-1, Document.ground)
+ THEMES.append(Document.ground)
class Ground:
def __init__(self, name, id):
@@ -33,7 +33,7 @@ class Ground:
def custom(self):
return True
- def read(self):
+ def serialize(self):
return theme.pixbuf2str(self._orig)
def thumb(self):
@@ -75,6 +75,7 @@ class JournalGround(Ground):
def __init__(self, jobject):
Ground.__init__(self, jobject.metadata['title'], jobject.object_id)
self._orig = theme.pixbuf(jobject.file_path)
+ THEMES.append(self)
THEMES = [
PreinstalledGround(_('Saturn'), 'images/backpics/bigbg01.gif'),
diff --git a/messenger.py b/messenger.py
new file mode 100644
index 0000000..f034ee8
--- /dev/null
+++ b/messenger.py
@@ -0,0 +1,284 @@
+# 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 cjson
+import logging
+import dbus
+from dbus.gobject_service import ExportedGObject
+from dbus.service import method, signal
+
+from sugar.presence import presenceservice
+
+import char
+import ground
+import sound
+from document import Document
+
+logger = logging.getLogger('cartoon-builder')
+
+SERVICE = 'org.sugarlabs.CartoonBuilder'
+IFACE = SERVICE
+PATH = '/org/sugarlabs/CartoonBuilder'
+
+class Slot:
+ def __init__(self, sender=None, raw=None):
+ if sender:
+ data = cjson.decode(raw)
+ self.seqno = data['seqno']
+ self.oid = data['oid']
+ self.sender = sender
+ else:
+ self.seqno = -1
+ self.oid = None
+ self.sender = None
+
+ def serialize(self):
+ return cjson.encode({
+ 'seqno': self.seqno,
+ 'oid' : self.oid})
+
+class Messenger(ExportedGObject):
+ def __init__(self, tube, initiator, view):
+ ExportedGObject.__init__(self, tube, PATH)
+
+ self.initiator = initiator
+ self._tube = tube
+ self._entered = False
+ self._slots = {}
+ self._view = view
+
+ self._view.connect('frame-changed', self._frame_changed_cb)
+ self._view.connect('ground-changed', self._ground_changed_cb)
+ self._view.connect('sound-changed', self._sound_changed_cb)
+ self._tube.watch_participants(self._participant_change_cb)
+
+ def _participant_change_cb(self, added, removed):
+ if not self._entered and added:
+ self.me = self._tube.get_unique_name()
+
+ slots = [('%s:%d' % (FRAME, i), f) \
+ for i, f in enumerate(Document.tape)] + \
+ [(GROUND, Document.ground), (SOUND, Document.sound)]
+ for i in slots:
+ self._slots[i[0]] = Slot()
+
+ if self.initiator:
+ self._tube.add_signal_receiver(self._ping_cb, '_ping', IFACE,
+ path=PATH, sender_keyword='sender')
+ for i in slots:
+ slot = self._slots[i[0]]
+ slot.seqno = 0
+ slot.oid = i[1].id
+ slot.sender = self.me
+ else:
+ self._pong_handle = self._tube.add_signal_receiver(
+ self._pong_cb, '_pong', IFACE, path=PATH,
+ sender_keyword='sender')
+ self._ping()
+
+
+ self._tube.add_signal_receiver(self._notify_cb, '_notify', IFACE,
+ path=PATH, sender_keyword='sender')
+ self._entered = True
+
+ # incomers' signal to retrieve initial snapshot
+ @signal(IFACE, signature='')
+ def _ping(self):
+ logger.debug('send ping')
+ pass
+
+ # object is ready to post snapshot to incomer
+ @signal(IFACE, signature='')
+ def _pong(self):
+ logger.debug('send pong')
+ pass
+
+ # slot was changed
+ @signal(IFACE, signature='ss')
+ def _notify(self, slot, raw):
+ pass
+
+ # the whole list of slots for incomers
+ @method(dbus_interface=IFACE, in_signature='', out_signature='a{ss}',
+ sender_keyword='sender')
+ def _snapshot(self, sender=None):
+ logger.debug('_snapshot requested from %s' % sender)
+ out = {}
+
+ for i, slot in self._slots.items():
+ out[i] = slot.serialize()
+
+ return out
+
+ # fetch content of specified object
+ @method(dbus_interface=IFACE, in_signature='ss', out_signature='say',
+ sender_keyword='sender', byte_arrays=True)
+ def _fetch(self, type, oid, sender=None):
+ logger.debug('_fetch requested from %s type=%s oid=%s' \
+ % (sender, type, oid))
+ return object_serialize(type, oid)
+
+ def _ping_cb(self, sender=None):
+ if sender == self.me:
+ return
+ logger.debug('_ping received from %s' % sender)
+ self._pong()
+
+ def _pong_cb(self, sender=None):
+ if sender == self.me:
+ return
+ logger.debug('_pong sent from %s' % sender)
+
+ # we've got source for _snapshot and don't need _pong anymore
+ self._tube.remove_signal_receiver(self._pong_handle)
+ self._pong_handle = None
+
+ remote = self._tube.get_object(sender, PATH)
+ rawlist = remote._snapshot()
+
+ logger.debug('snapshot received len=%d' % len(rawlist))
+
+ for slot, raw in rawlist.items():
+ self._receive(slot, raw, sender)
+
+ # we are ready to receive _snapshot requests
+ self._tube.add_signal_receiver(self._ping_cb, '_ping', IFACE,
+ path=PATH, sender_keyword='sender')
+
+ def _notify_cb(self, slot, raw, sender=None):
+ if sender == self.me:
+ return
+ logger.debug('_notify requested from %s' % sender)
+ self._receive(slot, raw, sender)
+
+ def _receive(self, slot, raw, sender):
+ cur = self._slots[slot]
+ new = Slot(sender, raw)
+
+ logger.debug('object received slot=%s seqno=%d sender=%s oid=%s from %s'
+ % (slot, new.seqno, new.sender, new.oid, sender))
+
+ if cur.seqno > new.seqno:
+ logger.debug('trying to rewrite newer value by older one')
+ return
+ elif cur.seqno == new.seqno:
+ # arrived value was sent at the same time as current one
+ if cur.sender > sender:
+ logger.debug('current value is higher ranked then arrived')
+ return
+ if cur.sender == self.me:
+ # we sent current and arrived value rewrites it
+ logger.debug('resend current with higher seqno')
+ self._send(slot, cur.oid)
+ return
+ else:
+ logger.debug('just discard low rank')
+ return
+ else:
+ logger.debug('accept higher seqno')
+
+ if new.oid and not object_find(slot, new.oid):
+ remote = self._tube.get_object(sender, PATH)
+ name, raw = remote._fetch(slot, new.oid, byte_arrays=True)
+ object_new(slot, new.oid, name, raw)
+
+ object_select(self._view, slot, new.oid)
+ self._slots[slot] = new
+
+ def _send(self, slot_num, oid):
+ slot = self._slots[slot_num]
+ slot.seqno += 1
+ slot.sender = self.me
+ slot.oid = oid
+ self._notify(slot_num, slot.serialize())
+
+ logger.debug('_send slot=%s oid=%s seqno=%d'
+ % (slot_num, oid, slot.seqno))
+
+ def _frame_changed_cb(self, sender, index, frame):
+ self._send('%s:%d' % (FRAME, index), frame and frame.id)
+
+ def _ground_changed_cb(self, sender, ground):
+ self._send(GROUND, ground.id)
+
+ def _sound_changed_cb(self, sender, sound):
+ self._send(SOUND, sound.id)
+
+FRAME = 'frame'
+GROUND = 'ground'
+SOUND = 'sound'
+
+OBJECTS = {
+ FRAME : char.THEMES[-1].frames,
+ GROUND : ground.THEMES,
+ SOUND : sound.THEMES }
+
+def object_find(type, oid):
+ if type.startswith(FRAME):
+ for c in char.THEMES:
+ if not c:
+ continue
+ for i in c.frames:
+ if i.id == oid:
+ return i
+ else:
+ for i in OBJECTS[type.split(':')[0]]:
+ if i and i.id == oid:
+ return i
+ return None
+
+def object_new(type, oid, name, raw):
+ logger.debug('add new object type=%s oid=%s' % (type, oid))
+
+ if type.startswith(FRAME):
+ object = char.RestoredFrame(oid, raw)
+ for i, frame in enumerate(OBJECTS[FRAME]):
+ if not frame.id:
+ OBJECTS[FRAME][i] = object
+ return
+ elif type.startswith(GROUND):
+ object = ground.RestoredGround(name, oid, raw)
+ elif type.startswith(SOUND):
+ object = sound.RestoredSound(name, oid, raw)
+ else:
+ logger.error('cannot create object of type %s' % type)
+ return
+
+ OBJECTS[type.split(':')[0]].append(object)
+
+def object_serialize(type, oid):
+ object = object_find(type, oid)
+
+ if object:
+ return (object.name, object.serialize())
+ else:
+ logger.error('cannot find object to serialize type=%s oid=%s' \
+ % (type, oid))
+ return ('', '')
+
+def object_select(view, type, oid):
+ if oid:
+ object = object_find(type, oid)
+ else:
+ object = None
+
+ if type.startswith(FRAME):
+ index = int(type.split(':')[1])
+ view.props.frame = (index, object)
+ elif type.startswith(GROUND):
+ view.props.ground = object
+ elif type.startswith(SOUND):
+ view.props.sound = object
+ else:
+ logger.error('cannot find object to select type=%s oid=%s' % (type, oid))
diff --git a/montage.py b/montage.py
index 840d3b7..a92f0d9 100644
--- a/montage.py
+++ b/montage.py
@@ -20,20 +20,27 @@
import gtk
import gobject
+import logging
+from gobject import SIGNAL_RUN_FIRST, TYPE_PYOBJECT
import theme
import char
import ground
import sound
from document import Document, clean
+from screen import Screen
from utils import *
+logger = logging.getLogger('cartoon-builder')
+
def play():
View.play_tape_num = 0
View.playing = gobject.timeout_add(View.delay, _play_tape)
def stop():
View.playing = None
+ View.screen.fgpixbuf = Document.tape[View.tape_selected].orig()
+ View.screen.draw()
def set_tempo(tempo):
View.delay = 10 + (10-int(tempo)) * 100
@@ -67,40 +74,10 @@ def _play_tape():
return True
class View(gtk.EventBox):
- class Screen(gtk.DrawingArea):
- def __init__(self):
- gtk.DrawingArea.__init__(self)
- self.gc = None # initialized in realize-event handler
- self.width = 0 # updated in size-allocate handler
- self.height = 0 # idem
- self.bgpixbuf = None
- self.fgpixbuf = None
- self.connect('size-allocate', self.on_size_allocate)
- self.connect('expose-event', self.on_expose_event)
- self.connect('realize', self.on_realize)
-
- def on_realize(self, widget):
- self.gc = widget.window.new_gc()
-
- def on_size_allocate(self, widget, allocation):
- self.height = self.width = min(allocation.width, allocation.height)
-
- def on_expose_event(self, widget, event):
- # This is where the drawing takes place
- if self.bgpixbuf:
- pixbuf = self.bgpixbuf
- if pixbuf.get_width != self.width:
- pixbuf = theme.scale(pixbuf, self.width)
- widget.window.draw_pixbuf(self.gc, pixbuf, 0, 0, 0, 0, -1, -1, 0, 0)
-
- if self.fgpixbuf:
- pixbuf = self.fgpixbuf
- if pixbuf.get_width != self.width:
- pixbuf = theme.scale(pixbuf, self.width)
- widget.window.draw_pixbuf(self.gc, pixbuf, 0, 0, 0, 0, -1, -1, 0, 0)
-
- def draw(self):
- self.queue_draw()
+ __gsignals__ = {
+ 'frame-changed' : (SIGNAL_RUN_FIRST, None, 2*[TYPE_PYOBJECT]),
+ 'ground-changed': (SIGNAL_RUN_FIRST, None, [TYPE_PYOBJECT]),
+ 'sound-changed' : (SIGNAL_RUN_FIRST, None, [TYPE_PYOBJECT]) }
screen = Screen()
play_tape_num = 0
@@ -109,33 +86,116 @@ class View(gtk.EventBox):
tape_selected = -1
tape = []
+ def set_frame(self, value):
+ tape_num, frame = value
+
+ if frame == None:
+ clean(tape_num)
+ View.tape[tape_num].child.set_from_pixbuf(theme.EMPTY_THUMB)
+ else:
+ if not frame.select():
+ return False
+
+ Document.tape[tape_num] = frame
+ View.tape[tape_num].child.set_from_pixbuf(frame.thumb())
+
+ if frame.custom():
+ index = [i for i, f in enumerate(char.THEMES[-1].frames)
+ if f == frame][0]
+ if index >= len(self._frames):
+ first = index / theme.FRAME_COLS * theme.FRAME_COLS
+ for i in range(first, first + theme.FRAME_COLS):
+ self._add_frame(i)
+
+ if self.char.custom():
+ self._frames[index].set_from_pixbuf(frame.thumb())
+
+ if View.tape_selected == tape_num:
+ self._tape_cb(None, None, tape_num)
+
+ return True
+
+ def set_ground(self, value):
+ self._set_combo(self._ground_combo, value)
+
+ def set_sound(self, value):
+ self._set_combo(self._sound_combo, value)
+
+ def _set_combo(self, combo, value):
+ try:
+ self._stop_emission = True
+ pos = -1
+
+ for i, item in enumerate(combo.get_model()):
+ if item[0] == value:
+ pos = i
+ break
+
+ if pos == -1:
+ combo.append_item(value, text = value.name,
+ size = (theme.THUMB_SIZE, theme.THUMB_SIZE),
+ pixbuf = value.thumb())
+ pos = len(combo.get_model())-1
+
+ combo.set_active(pos)
+ finally:
+ self._stop_emission = False
+
+ frame = gobject.property(type=object, getter=None, setter=set_frame)
+ ground = gobject.property(type=object, getter=None, setter=set_ground)
+ sound = gobject.property(type=object, getter=None, setter=set_sound)
+
+ def restore(self):
+ def new_combo(themes, cb, object = None, closure = None):
+ combo = ComboBox()
+ sel = 0
+
+ for i, o in enumerate(themes):
+ if o:
+ combo.append_item(o, text = o.name,
+ size = (theme.THUMB_SIZE, theme.THUMB_SIZE),
+ pixbuf = o.thumb())
+ if object and o.name == object.name:
+ sel = i
+ else:
+ combo.append_separator()
+
+ combo.connect('changed', cb, closure)
+ combo.set_active(sel)
+ combo.show()
+
+ return combo
+
+ self.controlbox.pack_start(new_combo(char.THEMES, self._char_cb),
+ False, False)
+ self._ground_combo = new_combo(ground.THEMES, self._combo_cb,
+ Document.ground, self._ground_cb)
+ self.controlbox.pack_start(self._ground_combo, False, False)
+ self._sound_combo = new_combo(sound.THEMES, self._combo_cb,
+ Document.sound, self._sound_cb)
+ self.controlbox.pack_start(self._sound_combo, False, False)
+
+ for i in range(theme.TAPE_COUNT):
+ View.tape[i].child.set_from_pixbuf(Document.tape[i].thumb())
+ self._tape_cb(None, None, 0)
+
+ return False
+
def __init__(self):
gtk.EventBox.__init__(self)
+ self.char = None
self._frames = []
self._prev_combo_selected = {}
+ self._stop_emission = False
# frames table
- self.table = gtk.Table(theme.FRAME_ROWS, columns=theme.FRAME_COLS,
+ self.table = gtk.Table(#theme.FRAME_ROWS, columns=theme.FRAME_COLS,
homogeneous=False)
- for y in range(theme.FRAME_ROWS):
- for x in range(theme.FRAME_COLS):
- image = gtk.Image()
- self._frames.append(image)
-
- image_box = gtk.EventBox()
- image_box.set_events(gtk.gdk.BUTTON_PRESS_MASK)
- image_box.connect('button_press_event', self._frame_cb,
- y * theme.FRAME_COLS + x)
- image_box.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(BLACK))
- image_box.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.color_parse(BLACK))
- image_box.props.border_width = 2
- image_box.set_size_request(theme.THUMB_SIZE, theme.THUMB_SIZE)
- image_box.add(image)
-
- self.table.attach(image_box, x, x+1, y, y+1)
+ for i in range(theme.FRAME_ROWS * theme.FRAME_COLS):
+ self._add_frame(i)
# frames box
@@ -165,7 +225,7 @@ class View(gtk.EventBox):
screen_pink.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(PINK))
screen_box = gtk.EventBox()
screen_box.set_border_width(5)
- screen_box.add(self.screen)
+ screen_box.add(View.screen)
screen_pink.add(screen_box)
screen_pink.props.border_width = theme.BORDER_WIDTH
@@ -263,44 +323,37 @@ class View(gtk.EventBox):
self.add(greenbox)
self.show_all()
- def restore(self):
- def new_combo(themes, cb, object = None, closure = None):
- combo = ComboBox()
- sel = 0
-
- for i, o in enumerate(themes):
- if o:
- combo.append_item(o, text = o.name,
- size = (theme.THUMB_SIZE, theme.THUMB_SIZE),
- pixbuf = o.thumb())
- if object and o.name == object.name:
- sel = i
- else:
- combo.append_separator()
+ def _add_frame(self, index):
+ y = index / theme.FRAME_COLS
+ x = index - y*theme.FRAME_COLS
+ logger.debug('add new frame x=%d y=%d index=%d' % (x, y, index))
- combo.connect('changed', cb, closure)
- combo.set_active(sel)
- combo.show()
+ image = gtk.Image()
+ image.show()
+ image.set_from_pixbuf(theme.EMPTY_THUMB)
+ self._frames.append(image)
- return combo
+ image_box = gtk.EventBox()
+ image_box.set_events(gtk.gdk.BUTTON_PRESS_MASK)
+ image_box.connect('button_press_event', self._frame_cb, index)
+ image_box.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(BLACK))
+ image_box.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.color_parse(BLACK))
+ image_box.props.border_width = 2
+ image_box.set_size_request(theme.THUMB_SIZE, theme.THUMB_SIZE)
+ image_box.add(image)
- self.controlbox.pack_start(new_combo(char.THEMES, self._char_cb),
- False, False)
- self.controlbox.pack_start(new_combo(ground.THEMES, self._combo_cb,
- Document.ground, self._ground_cb), False, False)
- self.controlbox.pack_start(new_combo(sound.THEMES, self._combo_cb,
- Document.sound, self._sound_cb), False, False)
+ if self.char and self.char.custom():
+ image_box.show()
- for i in range(theme.TAPE_COUNT):
- View.tape[i].child.set_from_pixbuf(Document.tape[i].thumb())
- self._tape_cb(None, None, 0)
+ self.table.attach(image_box, x, x+1, y, y+1)
- return False
+ return image
def _tape_cb(self, widget, event, index):
if event and event.button == 3:
- clean(index)
- View.tape[index].child.set_from_pixbuf(theme.EMPTY_THUMB)
+ self.set_frame((index, None))
+ self.emit('frame-changed', index, None)
+ return
tape = View.tape[index]
tape.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(YELLOW))
@@ -315,26 +368,32 @@ class View(gtk.EventBox):
gtk.gdk.color_parse(BLACK))
View.tape_selected = index
- self.screen.fgpixbuf = Document.tape[index].orig()
- self.screen.draw()
+ View.screen.fgpixbuf = Document.tape[index].orig()
+ View.screen.draw()
def _frame_cb(self, widget, event, i):
-
if event.button == 3:
self.char.clean(i)
self._frames[i].set_from_pixbuf(self.char.frames[i].thumb())
else:
- frame = self.char.frames[i]
- if frame.select():
- Document.tape[View.tape_selected] = frame
- View.tape[View.tape_selected].child.set_from_pixbuf(frame.thumb())
- self._frames[i].set_from_pixbuf(frame.thumb())
- self._tape_cb(None, None, View.tape_selected)
+ if i < len(self.char.frames):
+ frame = self.char.frames[i]
+ if not self.set_frame((View.tape_selected, frame)):
+ return
+ else:
+ frame = None
+ self.set_frame((View.tape_selected, None))
+
+ self.emit('frame-changed', View.tape_selected, frame)
def _char_cb(self, widget, closure):
self.char = widget.props.value
for i in range(len(self._frames)):
- self._frames[i].set_from_pixbuf(self.char.frames[i].thumb())
+ if i < len(self.char.frames):
+ self._frames[i].set_from_pixbuf(self.char.frames[i].thumb())
+ self._frames[i].parent.show()
+ else:
+ self._frames[i].parent.hide()
def _combo_cb(self, widget, cb):
choice = widget.props.value.select()
@@ -344,22 +403,25 @@ class View(gtk.EventBox):
return
if id(choice) != id(widget.props.value):
- pos = widget.get_active()
widget.append_item(choice, text = choice.name,
size = (theme.THUMB_SIZE, theme.THUMB_SIZE),
- pixbuf = choice.thumb(), position = pos)
- widget.set_active(pos)
+ pixbuf = choice.thumb())
+ widget.set_active(len(widget.get_model())-1)
self._prev_combo_selected[widget] = widget.get_active()
cb(choice)
def _ground_cb(self, choice):
- self.screen.bgpixbuf = choice.orig()
- self.screen.draw()
+ View.screen.bgpixbuf = choice.orig()
+ View.screen.draw()
Document.ground = choice
+ if not self._stop_emission:
+ self.emit('ground-changed', choice)
def _sound_cb(self, choice):
Document.sound = choice
+ if not self._stop_emission:
+ self.emit('sound-changed', choice)
def _screen_size_cb(self, widget, aloc):
size = min(aloc.width, aloc.height)
diff --git a/screen.py b/screen.py
new file mode 100644
index 0000000..29ed2b7
--- /dev/null
+++ b/screen.py
@@ -0,0 +1,58 @@
+# 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
+#
+
+### cartoonbuilder
+###
+### author: Ed Stoner (ed@whsd.net)
+### (c) 2007 World Wide Workshop Foundation
+
+import gtk
+
+import theme
+
+class Screen(gtk.DrawingArea):
+ def __init__(self):
+ gtk.DrawingArea.__init__(self)
+ self.gc = None # initialized in realize-event handler
+ self.width = 0 # updated in size-allocate handler
+ self.height = 0 # idem
+ self.bgpixbuf = None
+ self.fgpixbuf = None
+ self.connect('size-allocate', self.on_size_allocate)
+ self.connect('expose-event', self.on_expose_event)
+ self.connect('realize', self.on_realize)
+
+ def on_realize(self, widget):
+ self.gc = widget.window.new_gc()
+
+ def on_size_allocate(self, widget, allocation):
+ self.height = self.width = min(allocation.width, allocation.height)
+
+ def on_expose_event(self, widget, event):
+ # This is where the drawing takes place
+ if self.bgpixbuf:
+ pixbuf = self.bgpixbuf
+ if pixbuf.get_width != self.width:
+ pixbuf = theme.scale(pixbuf, self.width)
+ widget.window.draw_pixbuf(self.gc, pixbuf, 0, 0, 0, 0, -1, -1, 0, 0)
+
+ if self.fgpixbuf:
+ pixbuf = self.fgpixbuf
+ if pixbuf.get_width != self.width:
+ pixbuf = theme.scale(pixbuf, self.width)
+ widget.window.draw_pixbuf(self.gc, pixbuf, 0, 0, 0, 0, -1, -1, 0, 0)
+
+ def draw(self):
+ self.queue_draw()
diff --git a/shared.py b/shared.py
new file mode 100644
index 0000000..98fb965
--- /dev/null
+++ b/shared.py
@@ -0,0 +1,130 @@
+# 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 logging
+import telepathy
+from gobject import property, SIGNAL_RUN_FIRST, TYPE_PYOBJECT
+
+from sugar.activity.activity import Activity
+from sugar.presence.sugartubeconn import SugarTubeConnection
+
+logger = logging.getLogger('cartoon-builder')
+
+class CanvasActivity(Activity):
+ __gsignals__ = {
+ 'init' : (SIGNAL_RUN_FIRST, None, []) }
+
+ def __init__(self, canvas, *args):
+ Activity.__init__(self, *args)
+
+ self._inited = False
+
+ # XXX do it after(possible) read_file() invoking
+ # have to rely on calling read_file() from map_cb in sugar-toolkit
+ canvas.connect_after('map', self._map_cb)
+ self.set_canvas(canvas)
+
+ def get_inited(self):
+ return self._inited
+
+ inited = property(type=bool, default=False, getter=get_inited, setter=None)
+
+ def _map_cb(self, widget):
+ self._inited = True
+ self.emit('init')
+
+class SharedActivity(CanvasActivity):
+ __gsignals__ = {
+ 'tube' : (SIGNAL_RUN_FIRST, None, 2*[TYPE_PYOBJECT]) }
+
+ def __init__(self, canvas, service, *args):
+ CanvasActivity.__init__(self, canvas, *args)
+
+ self.service = service
+ self._postpone_tubes = []
+
+ self.connect('init', self._init_sharedactivity_cb)
+ self.connect('shared', self._shared_cb)
+
+ # Owner.props.key
+ if self._shared_activity:
+ # We are joining the activity
+ self.connect('joined', self._joined_cb)
+ if self.get_shared():
+ # We've already joined
+ self._joined_cb()
+
+ def _init_sharedactivity_cb(self):
+ for i in self._postpone_tubes:
+ self.emit('tube', i, self._initiating)
+ self._postpone_tubes = []
+
+ def _shared_cb(self, activity):
+ logger.debug('My activity was shared')
+ self._initiating = True
+ self._sharing_setup()
+
+ logger.debug('This is my activity: making a tube...')
+ id = self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
+ self.service, {})
+
+ def _joined_cb(self, activity):
+ if not self._shared_activity:
+ return
+
+ logger.debug('Joined an existing shared activity')
+
+ self._initiating = False
+ self._sharing_setup()
+
+ logger.debug('This is not my activity: waiting for a tube...')
+ self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
+ reply_handler=self._list_tubes_reply_cb,
+ error_handler=self._list_tubes_error_cb)
+
+ def _sharing_setup(self):
+ if self._shared_activity is None:
+ logger.error('Failed to share or join activity')
+ return
+ self._conn = self._shared_activity.telepathy_conn
+ self._tubes_chan = self._shared_activity.telepathy_tubes_chan
+ self._text_chan = self._shared_activity.telepathy_text_chan
+
+ self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube', self._new_tube_cb)
+
+ def _list_tubes_reply_cb(self, tubes):
+ for tube_info in tubes:
+ self._new_tube_cb(*tube_info)
+
+ def _list_tubes_error_cb(self, e):
+ logger.error('ListTubes() failed: %s', e)
+
+ def _new_tube_cb(self, id, initiator, type, service, params, state):
+ logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
+ 'params=%r state=%d', id, initiator, type, service,
+ params, state)
+
+ if (type == telepathy.TUBE_TYPE_DBUS and
+ service == self.service):
+ if state == telepathy.TUBE_STATE_LOCAL_PENDING:
+ self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
+
+ tube_conn = SugarTubeConnection(self._conn,
+ self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
+ id, group_iface=self._text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
+
+ if self.get_inited():
+ self.emit('tube', tube_conn, self._initiating)
+ else:
+ self._postpone_tubes.append(tube_conn)
diff --git a/sound.py b/sound.py
index 7beb9e8..7819b93 100644
--- a/sound.py
+++ b/sound.py
@@ -27,7 +27,7 @@ def load():
from document import Document
if Document.sound and Document.sound.custom():
- THEMES.insert(-1, Document.sound)
+ THEMES.append(Document.sound)
class Sound:
playing = False
@@ -43,7 +43,7 @@ class Sound:
def custom(self):
return True
- def read(self):
+ def serialize(self):
return file(self._soundfile, 'r').read()
def thumb(self):
@@ -67,12 +67,12 @@ class PreinstalledSound(Sound):
class MuteSound(Sound):
def __init__(self, name):
- Sound.__init__(self, name, None, None, theme.SOUND_MUTE)
+ Sound.__init__(self, name, 'mute', None, theme.SOUND_MUTE)
def custom(self):
return False
- def read(self):
+ def serialize(self):
return ''
def select(self):
@@ -101,6 +101,7 @@ class JournalSound(Sound):
Sound.__init__(self, jobject.metadata['title'],
jobject.object_id, soundfile, theme.SOUND_CUSTOM)
shutil.copy(jobject.file_path, soundfile)
+ THEMES.append(self)
THEMES = [
PreinstalledSound(_('Gobble'), 'sounds/gobble.wav'),
diff --git a/toolbars.py b/toolbars.py
deleted file mode 100644
index 98e1bbb..0000000
--- a/toolbars.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# 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
-from gettext import gettext as _
-
-from sugar.graphics.toolbutton import ToolButton
-from sugar.graphics.toggletoolbutton import ToggleToolButton
-
-import montage
-import lessons
-import sound
-from utils import *
-
-class MontageToolbar(gtk.Toolbar):
- def __init__(self):
- gtk.Toolbar.__init__(self)
-
- self.playButton = ToggleToolButton('media-playback-start')
- self.playButton.connect('toggled', self._play_cb)
- self.insert(self.playButton, -1)
- self.playButton.set_tooltip(_('Play / Pause'))
-
- # Play button Image
- self.playButtonImg = gtk.Image()
- self.playButtonImg.show()
- self.playButtonImg.set_from_icon_name('media-playback-start', gtk.ICON_SIZE_LARGE_TOOLBAR)
-
- # Pause button Image
- self.pauseButtonImg = gtk.Image()
- self.pauseButtonImg.show()
- self.pauseButtonImg.set_from_icon_name('media-playback-pause', gtk.ICON_SIZE_LARGE_TOOLBAR)
-
- tempo = TempoSlider(0, 10)
- tempo.adjustment.connect("value-changed", self._tempo_cb)
- tempo.set_size_request(250, -1)
- tempo.set_value(5)
- tempo_item = gtk.ToolItem()
- tempo_item.add(tempo)
- self.insert(tempo_item, -1)
-
- separator = gtk.SeparatorToolItem()
- self.insert(separator,-1)
-
- clear_tape = ToolButton('sl-reset')
- clear_tape.connect('clicked', self._clear_tape_cb)
- clear_tape.set_tooltip(_(''))
- self.insert(clear_tape, -1)
-
- self.show_all()
-
- def _clear_tape_cb(self, widget):
- montage.clear_tape()
-
- def _tempo_cb(self, widget):
- montage.set_tempo(widget.value)
-
- def _play_cb(self, widget):
- if widget.get_active():
- widget.set_icon_widget(self.pauseButtonImg)
- sound.play()
- montage.play()
- else:
- widget.set_icon_widget(self.playButtonImg)
- sound.stop()
- montage.stop()
-
-class LessonsToolbar(gtk.Toolbar):
- def __init__(self):
- gtk.Toolbar.__init__(self)
- self._mask = False
-
- for lesson in lessons.THEMES:
- button = gtk.ToggleToolButton()
- button.set_label(lesson.name)
- button.connect('clicked', self._lessons_cb, lesson)
- self.insert(button, -1)
-
- self.get_nth_item(0).set_active(True)
- self.show_all()
-
- def _lessons_cb(self, widget, lesson):
- if self._mask:
- return
- self._mask = True
-
- for i, j in enumerate(lessons.THEMES):
- if j != lesson:
- self.get_nth_item(i).set_active(False)
-
- widget.props.active = True
- lesson.change()
- self._mask = False