Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRafael Ortiz <rafael@activitycentral.com>2012-07-22 20:53:48 (GMT)
committer Rafael Ortiz <rafael@activitycentral.com>2012-07-22 20:53:48 (GMT)
commit2ba5c5514955cdd87308b2f67d3d329944e714c3 (patch)
tree88432423b830e562d10dfd987291459175034435
parent9e2cdff0c1416e6c416025b5ac922573dff63ca2 (diff)
parentca1ee28844fda1099036a5138cc35834c8f62f31 (diff)
Merge branch 'master' of git.sugarlabs.org:speak/mainline
-rw-r--r--.gitignore6
-rw-r--r--activity.py138
-rw-r--r--brain.py50
-rw-r--r--chat.py160
-rw-r--r--chatbox.py364
-rw-r--r--espeak_cmd.py4
-rw-r--r--espeak_gst.py2
-rw-r--r--eye.py24
-rw-r--r--face.py50
-rw-r--r--fft_mouth.py71
-rw-r--r--glasses.py24
-rw-r--r--messenger.py20
-rw-r--r--mouth.py25
-rw-r--r--po/bi.po74
-rw-r--r--po/da.po120
-rw-r--r--po/de.po6
-rw-r--r--po/en.po120
-rw-r--r--po/en_GB.po193
-rw-r--r--po/en_US.po193
-rw-r--r--po/fr.po4
-rw-r--r--po/hus.po8
-rw-r--r--po/hy.po4
-rw-r--r--po/pt.po6
-rw-r--r--po/quz.po9
-rw-r--r--po/zh_CN.po122
-rw-r--r--roundbox.py100
-rwxr-xr-xsetup.py6
-rw-r--r--shared_activity.py115
-rw-r--r--toolkit/__init__.py16
-rw-r--r--toolkit/activity.py331
-rw-r--r--toolkit/activity_widgets.py397
-rw-r--r--toolkit/chooser.py69
-rw-r--r--toolkit/combobox.py (renamed from combobox.py)0
-rw-r--r--toolkit/internals/__init__.py16
-rw-r--r--toolkit/internals/palettewindow.py976
-rw-r--r--toolkit/json.py35
-rw-r--r--toolkit/pixbuf.py116
-rw-r--r--toolkit/radiopalette.py109
-rw-r--r--toolkit/scrolledbox.py191
-rw-r--r--toolkit/tarball.py125
-rw-r--r--toolkit/temposlider.py211
-rw-r--r--toolkit/toolbarbox.py333
-rw-r--r--toolkit/toolitem.py (renamed from toolitem.py)2
-rw-r--r--voice.py63
-rw-r--r--waveform_mouth.py43
45 files changed, 3784 insertions, 1267 deletions
diff --git a/.gitignore b/.gitignore
index 1e4c0c4..b9cb894 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,2 @@
-*.pyc
-*.pyo
-*.mo
-*~
+dist
+.0sugar
diff --git a/activity.py b/activity.py
index d4f0e2a..b973ca9 100644
--- a/activity.py
+++ b/activity.py
@@ -31,17 +31,16 @@ import pango
import cjson
from gettext import gettext as _
-from sugar.graphics.toolbarbox import ToolbarButton
+from sugar.graphics.toolbutton import ToolButton
from sugar.graphics.toggletoolbutton import ToggleToolButton
from sugar.graphics.radiotoolbutton import RadioToolButton
-from combobox import ComboBox
-from sugar.graphics.toolbarbox import ToolbarBox
-from shared_activity import SharedActivity
-from sugar.activity.widgets import ActivityToolbarButton
-from sugar.activity.widgets import StopButton
+from toolkit.toolitem import ToolWidget
+from toolkit.combobox import ComboBox
+from toolkit.toolbarbox import ToolbarBox
+from toolkit.activity import SharedActivity
+from toolkit.activity_widgets import *
-from toolitem import ToolWidget
import eye
import glasses
import mouth
@@ -60,39 +59,13 @@ MODE_TYPE = 1
MODE_BOT = 2
MODE_CHAT = 3
-_NEW_INSTANCE = 0
-_NEW_INSTANCE = 1
-_PRE_INSTANCE = 2
-_POST_INSTANCE = 3
-
-
-class CursorFactory:
-
- __shared_state = {"cursors": {}}
-
- def __init__(self):
- self.__dict__ = self.__shared_state
-
- def get_cursor(self, cur_type):
- if not cur_type in self.cursors:
- cur = gtk.gdk.Cursor(cur_type)
- self.cursors[cur_type] = cur
- return self.cursors[cur_type]
-
class SpeakActivity(SharedActivity):
def __init__(self, handle):
self.notebook = gtk.Notebook()
- self.notebook.connect_after('map', self.__map_canvasactivity_cb)
SharedActivity.__init__(self, self.notebook, SERVICE, handle)
- self._cursor = None
- self.set_cursor(gtk.gdk.LEFT_PTR)
- self.__resume_filename = None
- self.__postponed_share = []
- self.__on_save_instance = []
- self.__state = _NEW_INSTANCE
self._mode = MODE_TYPE
self.numeyesadj = None
@@ -126,8 +99,8 @@ class SpeakActivity(SharedActivity):
# desktop
self.notebook.show()
- self.notebook.props.show_border = False
- self.notebook.props.show_tabs = False
+ self.notebook.props.show_border=False
+ self.notebook.props.show_tabs=False
box.show_all()
self.notebook.append_page(box)
@@ -150,7 +123,7 @@ class SpeakActivity(SharedActivity):
self.voices = ComboBox()
for name in sorted(voice.allVoices().keys()):
vn = voice.allVoices()[name]
- n = name[:26] + ".."
+ n = name [ : 26 ] + ".."
self.voices.append_item(vn, n)
self.voices.select(voice.defaultVoice())
@@ -203,71 +176,10 @@ class SpeakActivity(SharedActivity):
toolbox.show_all()
self.toolbar_box = toolbox
- def _share(self, tube_conn, initiator):
- logging.debug('Activity._share state=%s' % self.__state)
-
- if self.__state == _NEW_INSTANCE:
- self.__postponed_share.append((tube_conn, initiator))
- self.__state = _PRE_INSTANCE
- elif self.__state == _PRE_INSTANCE:
- self.__postponed_share.append((tube_conn, initiator))
- self.__instance()
- elif self.__state == _POST_INSTANCE:
- self.share_instance(tube_conn, initiator)
-
- def set_cursor(self, cursor):
- if not isinstance(cursor, gtk.gdk.Cursor):
- cursor = CursorFactory().get_cursor(cursor)
-
- if self._cursor != cursor:
- self._cursor = cursor
- self.window.set_cursor(self._cursor)
-
- def __map_canvasactivity_cb(self, widget):
- logging.debug('Activity.__map_canvasactivity_cb state=%s' % \
- self.__state)
-
- if self.__state == _NEW_INSTANCE:
- self.__instance()
- elif self.__state == _NEW_INSTANCE:
- self.__state = _PRE_INSTANCE
- elif self.__state == _PRE_INSTANCE:
- self.__instance()
-
- return False
-
- def __instance(self):
- logging.debug('Activity.__instance')
-
- if self.__resume_filename:
- self.resume_instance(self.__resume_filename)
- else:
- self.new_instance()
-
- for i in self.__postponed_share:
- self.share_instance(*i)
- self.__postponed_share = []
-
- self.__state = _POST_INSTANCE
-
- def read_file(self, file_path):
- self.__resume_filename = file_path
- if self.__state == _NEW_INSTANCE:
- self.__state = _PRE_INSTANCE
- elif self.__state == _PRE_INSTANCE:
- self.__instance()
-
- def write_file(self, file_path):
- for cb, args in self.__on_save_instance:
- cb(*args)
- self.save_instance(file_path)
-
def new_instance(self):
self.voices.connect('changed', self.__changed_voices_cb)
- self.pitchadj.connect("value_changed", self.pitch_adjusted_cb,
- self.pitchadj)
- self.rateadj.connect("value_changed", self.rate_adjusted_cb,
- self.rateadj)
+ self.pitchadj.connect("value_changed", self.pitch_adjusted_cb, self.pitchadj)
+ self.rateadj.connect("value_changed", self.rate_adjusted_cb, self.rateadj)
self.mouth_shape_combo.connect('changed', self.mouth_changed_cb, False)
self.mouth_changed_cb(self.mouth_shape_combo, True)
self.numeyesadj.connect("value_changed", self.eyes_changed_cb, False)
@@ -336,7 +248,7 @@ class SpeakActivity(SharedActivity):
voicebar = gtk.Toolbar()
self.pitchadj = gtk.Adjustment(self.face.status.pitch, 0,
- espeak.PITCH_MAX, 1, espeak.PITCH_MAX / 10, 0)
+ espeak.PITCH_MAX, 1, espeak.PITCH_MAX/10, 0)
pitchbar = gtk.HScale(self.pitchadj)
pitchbar.set_draw_value(False)
# pitchbar.set_inverted(True)
@@ -348,8 +260,8 @@ class SpeakActivity(SharedActivity):
label_text=_('Pitch:'))
voicebar.insert(pitchbar_toolitem, -1)
- self.rateadj = gtk.Adjustment(self.face.status.rate, 0,
- espeak.RATE_MAX, 1, espeak.RATE_MAX / 10, 0)
+ self.rateadj = gtk.Adjustment(self.face.status.rate, 0, espeak.RATE_MAX,
+ 1, espeak.RATE_MAX / 10, 0)
ratebar = gtk.HScale(self.rateadj)
ratebar.set_draw_value(False)
# ratebar.set_inverted(True)
@@ -377,8 +289,7 @@ class SpeakActivity(SharedActivity):
self.mouth_shape_combo = ComboBox()
self.mouth_shape_combo.append_item(mouth.Mouth, _("Simple"))
- self.mouth_shape_combo.append_item(waveform_mouth.WaveformMouth,
- _("Waveform"))
+ self.mouth_shape_combo.append_item(waveform_mouth.WaveformMouth, _("Waveform"))
self.mouth_shape_combo.append_item(fft_mouth.FFTMouth, _("Frequency"))
self.mouth_shape_combo.set_active(0)
@@ -446,15 +357,15 @@ class SpeakActivity(SharedActivity):
keyname = gtk.gdk.keyval_name(event.keyval)
if keyname == "Up":
index = self.entrycombo.get_active()
- if index > 0:
- index -= 1
+ if index>0:
+ index-=1
self.entrycombo.set_active(index)
- self.entry.select_region(0, -1)
+ self.entry.select_region(0,-1)
return True
elif keyname == "Down":
index = self.entrycombo.get_active()
- if index < len(self.entrycombo.get_model()) - 1:
- index += 1
+ if index<len(self.entrycombo.get_model())-1:
+ index+=1
self.entrycombo.set_active(index)
self.entry.select_region(0, -1)
return True
@@ -473,16 +384,15 @@ class SpeakActivity(SharedActivity):
else:
self.face.say(text)
- # add this text to our history unless
- # it is the same as the last item
+ # add this text to our history unless it is the same as the last item
history = self.entrycombo.get_model()
- if len(history) == 0 or history[-1][0] != text:
+ if len(history)==0 or history[-1][0] != text:
self.entrycombo.append_text(text)
# don't let the history get too big
- while len(history) > 20:
+ while len(history)>20:
self.entrycombo.remove_text(0)
# select the new item
- self.entrycombo.set_active(len(history) - 1)
+ self.entrycombo.set_active(len(history)-1)
# select the whole text
entry.select_region(0, -1)
diff --git a/brain.py b/brain.py
index d98578f..5223f4b 100644
--- a/brain.py
+++ b/brain.py
@@ -1,23 +1,21 @@
# HablarConSara.activity
# A simple hack to attach a chatterbot to speak activity
-# Copyright (C) 2008
-# Sebastian Silva Fundacion FuenteLibre sebastian@fuentelibre.org
+# Copyright (C) 2008 Sebastian Silva Fundacion FuenteLibre sebastian@fuentelibre.org
#
# Style and structure taken from Speak.activity Copyright (C) Joshua Minor
#
-# HablarConSara.activity 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 3 of the License, or
-# (at your option) any later version.
+# HablarConSara.activity 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 3 of the License, or
+# (at your option) any later version.
#
-# HablarConSara.activity 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.
+# HablarConSara.activity 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 HablarConSara.activity.
-# If not, see <http://www.gnu.org/licenses/>.
+# You should have received a copy of the GNU General Public License
+# along with HablarConSara.activity. If not, see <http://www.gnu.org/licenses/>.
import gtk
import gobject
@@ -26,19 +24,20 @@ from gettext import gettext as _
import logging
logger = logging.getLogger('speak')
-from combobox import ComboBox
+from toolkit.combobox import ComboBox
import aiml
import voice
-BOTS = {_('Spanish'): {'name': 'Sara',
- 'brain': 'bot/sara.brn',
- 'predicates': {'nombre_bot': 'Sara',
- 'botmaster': 'La comunidad Azucar'}},
- _('English'): {'name': 'Alice',
- 'brain': 'bot/alice.brn',
- 'predicates': {'name': 'Alice',
- 'master': 'The Sugar Community'}}}
+BOTS = {
+ _('Spanish'): { 'name': 'Sara',
+ 'brain': 'bot/sara.brn',
+ 'predicates': { 'nombre_bot': 'Sara',
+ 'botmaster': 'La comunidad Azucar' } },
+ _('English'): { 'name': 'Alice',
+ 'brain': 'bot/alice.brn',
+ 'predicates': { 'name': 'Alice',
+ 'master': 'The Sugar Community' } } }
def get_mem_info(tag):
@@ -86,7 +85,7 @@ def load(activity, voice, sorry=None):
if voice == _kernel_voice:
return False
- old_cursor = activity._cursor
+ old_cursor = activity.get_cursor()
activity.set_cursor(gtk.gdk.WATCH)
def load_brain():
@@ -123,9 +122,8 @@ def load(activity, voice, sorry=None):
activity.set_cursor(old_cursor)
if is_first_session:
- hello = _("Hello, I'm a robot \"%s\". " +\
- "Please ask me any question.") %\
- BOTS[voice.friendlyname]['name']
+ hello = _("Hello, I'm a robot \"%s\". Please ask me any question.") \
+ % BOTS[voice.friendlyname]['name']
if sorry:
hello += ' ' + sorry
activity.face.say_notification(hello)
diff --git a/chat.py b/chat.py
index a61822d..61cd248 100644
--- a/chat.py
+++ b/chat.py
@@ -16,11 +16,12 @@
import gtk
import pango
+import hippo
import logging
from gettext import gettext as _
import sugar.graphics.style as style
-from roundbox import RoundBox
+from sugar.graphics.roundbox import CanvasRoundBox
from sugar.graphics.toggletoolbutton import ToggleToolButton
import eye
@@ -44,9 +45,9 @@ ENTRY_XPAD = 0
ENTRY_YPAD = 7
-class View(gtk.EventBox):
+class View(hippo.Canvas):
def __init__(self):
- gtk.EventBox.__init__(self)
+ hippo.Canvas.__init__(self)
self.messenger = None
self.me = None
@@ -56,18 +57,17 @@ class View(gtk.EventBox):
# buddies box
- self._buddies_list = gtk.VBox()
- self._buddies_list.set_homogeneous(False)
- self._buddies_list.props.spacing = ENTRY_YPAD
+ self._buddies_list = hippo.CanvasBox(
+ background_color = BUDDIES_COLOR.get_int(),
+ box_width = BUDDIES_WIDTH,
+ padding = ENTRY_YPAD,
+ spacing = ENTRY_YPAD
+ )
- self._buddies_box = gtk.ScrolledWindow()
- self._buddies_box.set_policy(gtk.POLICY_ALWAYS,
- gtk.POLICY_NEVER)
- evbox = gtk.EventBox()
- evbox.modify_bg(gtk.STATE_NORMAL, BUDDIES_COLOR.get_gdk_color())
- evbox.add(self._buddies_list)
- evbox.show()
- self._buddies_box.add_with_viewport(evbox)
+ self._buddies_box = hippo.CanvasScrollbars()
+ self._buddies_box.set_policy(hippo.ORIENTATION_HORIZONTAL,
+ hippo.SCROLLBAR_NEVER)
+ self._buddies_box.set_root(self._buddies_list)
# chat entry
@@ -83,37 +83,44 @@ class View(gtk.EventBox):
chat_post.connect('key-press-event', self._key_press_cb)
chat_post.props.wrap_mode = gtk.WRAP_WORD_CHAR
chat_post.set_size_request(-1, BUDDY_SIZE - ENTRY_YPAD * 2)
- chat_post_box = RoundBox()
- chat_post_box.background_color = style.COLOR_WHITE
- chat_post_box.border_color = ENTRY_COLOR
- chat_post_box.pack_start(chat_post, True, True, ENTRY_XPAD)
-
- chat_entry = RoundBox()
- chat_entry.set_border_width(ENTRY_YPAD)
- chat_entry.background_color = ENTRY_COLOR
- chat_entry.border_color = style.COLOR_WHITE
- chat_entry.pack_start(my_face_widget, False, True, 0)
- separator = gtk.EventBox()
- separator.modify_bg(gtk.STATE_NORMAL, ENTRY_COLOR.get_gdk_color())
- separator.set_size_request(ENTRY_YPAD, -1)
- separator.show()
- chat_entry.pack_start(separator, False, False)
- chat_entry.pack_start(chat_post_box, True, True, ENTRY_XPAD)
-
- evbox = gtk.EventBox()
- evbox.modify_bg(gtk.STATE_NORMAL, style.COLOR_WHITE.get_gdk_color())
- chat_box = gtk.VBox()
- chat_box.pack_start(self._chat, True, True)
- chat_box.pack_start(chat_entry, False, True)
- evbox.add(chat_box)
+ chat_post_box = CanvasRoundBox(
+ background_color = style.COLOR_WHITE.get_int(),
+ padding_left = ENTRY_XPAD,
+ padding_right = ENTRY_XPAD,
+ padding_top = ENTRY_YPAD,
+ padding_bottom = ENTRY_YPAD
+ )
+ chat_post_box.props.border_color = ENTRY_COLOR.get_int()
+ chat_post_box.append(hippo.CanvasWidget(widget=chat_post),
+ hippo.PACK_EXPAND)
+
+ chat_entry = CanvasRoundBox(
+ background_color = ENTRY_COLOR.get_int(),
+ padding_left = ENTRY_XPAD,
+ padding_right = ENTRY_XPAD,
+ padding_top = ENTRY_YPAD,
+ padding_bottom = ENTRY_YPAD,
+ spacing = ENTRY_YPAD
+ )
+ chat_entry.props.orientation = hippo.ORIENTATION_HORIZONTAL
+ chat_entry.props.border_color = style.COLOR_WHITE.get_int()
+ chat_entry.append(my_face_widget)
+ chat_entry.append(chat_post_box, hippo.PACK_EXPAND)
+
+ chat_box = hippo.CanvasBox(
+ orientation = hippo.ORIENTATION_VERTICAL,
+ background_color = style.COLOR_WHITE.get_int(),
+ )
+ chat_box.append(self._chat, hippo.PACK_EXPAND)
+ chat_box.append(chat_entry)
# desk
- self._desk = gtk.HBox()
- self._desk.pack_start(evbox, True, True)
- self._desk.show_all()
+ self._desk = hippo.CanvasBox()
+ self._desk.props.orientation = hippo.ORIENTATION_HORIZONTAL
+ self._desk.append(chat_box, hippo.PACK_EXPAND)
- self.add(self._desk)
+ self.set_root(self._desk)
def update(self, status):
self.me.update(status)
@@ -154,34 +161,46 @@ class View(gtk.EventBox):
def shut_up(self):
for i in self._buddies.values():
- i['face'].shut_up()
- self.me.shut_up()
+ i['face'].shut_up();
+ self.me.shut_up();
def _add_buddy(self, buddy):
- evbox = gtk.EventBox()
- evbox.modify_bg(gtk.STATE_NORMAL, BUDDIES_COLOR.get_gdk_color())
- box = gtk.HBox()
+ box = hippo.CanvasBox(
+ orientation = hippo.ORIENTATION_HORIZONTAL,
+ background_color = BUDDIES_COLOR.get_int(),
+ spacing = ENTRY_YPAD
+ )
buddy_face, buddy_widget = self._new_face(buddy, BUDDIES_COLOR)
- char_box = gtk.VBox()
- nick = gtk.Label(buddy.props.nick)
- lang = gtk.Label()
- char_box.pack_start(nick)
- char_box.pack_start(lang)
-
- box.pack_start(buddy_widget, False, False, ENTRY_YPAD)
- box.pack_start(char_box, True, True, ENTRY_YPAD)
+ char_box = hippo.CanvasBox(
+ orientation = hippo.ORIENTATION_VERTICAL,
+ )
+ nick = hippo.CanvasText(
+ text = buddy.props.nick,
+ xalign = hippo.ALIGNMENT_START,
+ yalign = hippo.ALIGNMENT_START
+ )
+ lang = hippo.CanvasText(
+ text = '',
+ xalign = hippo.ALIGNMENT_START,
+ yalign = hippo.ALIGNMENT_START
+ )
+ char_box.append(nick)
+ char_box.append(lang)
+
+ box.append(buddy_widget)
+ box.append(char_box, hippo.PACK_EXPAND)
self._buddies[buddy] = {
'box': box,
'face': buddy_face,
'lang': lang
}
- self._buddies_list.pack_start(box)
+ self._buddies_list.append(box)
if len(self._buddies) == 1:
- self._desk.pack_start(self._buddies_box)
+ self._desk.append(self._buddies_box)
def _key_press_cb(self, widget, event):
if event.keyval == gtk.keysyms.Return:
@@ -207,20 +226,21 @@ class View(gtk.EventBox):
buddy_face = face.View(fill_color)
buddy_face.show_all()
- inner = RoundBox()
- inner.set_border_width(BUDDY_PAD)
- inner.background_color = fill_color
- inner.border_color = fill_color
- inner.pack_start(buddy_face, True, True, 0)
- inner.border = BUDDY_PAD
-
- outer = RoundBox()
- outer.set_border_width(BUDDY_PAD)
- outer.background_color = stroke_color
- outer.set_size_request(BUDDY_SIZE, BUDDY_SIZE)
- outer.border_color = stroke_color
- outer.pack_start(inner, True, True, 0)
- outer.border = BUDDY_PAD
+ inner = CanvasRoundBox(
+ background_color = fill_color.get_int(),
+ )
+ inner.props.border_color = fill_color.get_int()
+ inner.append(hippo.CanvasWidget(widget=buddy_face), hippo.PACK_EXPAND)
+ inner.props.border = BUDDY_PAD
+
+ outer = CanvasRoundBox(
+ background_color = stroke_color.get_int(),
+ box_width = BUDDY_SIZE,
+ box_height = BUDDY_SIZE,
+ )
+ outer.props.border_color = stroke_color.get_int()
+ outer.append(inner, hippo.PACK_EXPAND)
+ outer.props.border = BUDDY_PAD
return (buddy_face, outer)
diff --git a/chatbox.py b/chatbox.py
index fdbff56..671ba09 100644
--- a/chatbox.py
+++ b/chatbox.py
@@ -17,20 +17,21 @@
# This code is a stripped down version of the Chat
import gtk
+import hippo
import logging
import pango
import re
from datetime import datetime
+from gobject import SIGNAL_RUN_FIRST, TYPE_PYOBJECT
from gettext import gettext as _
-from sugar.graphics import style
-from sugar.graphics.palette import Palette
-from sugar.graphics.palette import CanvasInvoker
-from sugar.graphics.palette import MouseSpeedDetector
+import sugar.graphics.style as style
+from sugar.graphics.roundbox import CanvasRoundBox
+from sugar.graphics.palette import Palette, CanvasInvoker
from sugar.presence import presenceservice
+from sugar.graphics.style import (Color, COLOR_BLACK, COLOR_WHITE)
from sugar.graphics.menuitem import MenuItem
from sugar.activity.activity import get_activity_root
-from roundbox import RoundBox
logger = logging.getLogger('speak')
@@ -39,173 +40,9 @@ URL_REGEXP = re.compile('((http|ftp)s?://)?'
'(:[1-9][0-9]{0,4})?(/[-a-zA-Z0-9/%~@&_+=;:,.?#]*[a-zA-Z0-9/])?')
-class TextBox(gtk.TextView):
-
- hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
-
- def __init__(self, color, bg_color, lang_rtl):
- self._lang_rtl = lang_rtl
- gtk.TextView.__init__(self)
- self.set_editable(False)
- self.set_cursor_visible(False)
- self.set_wrap_mode(gtk.WRAP_WORD_CHAR)
- self.get_buffer().set_text("", 0)
- self.iter_text = self.get_buffer().get_iter_at_offset(0)
- self.fg_tag = self.get_buffer().create_tag("foreground_color",
- foreground=color.get_html())
- self._subscript_tag = self.get_buffer().create_tag('subscript',
- rise=-7 * pango.SCALE) # in pixels
- self._empty = True
- self.palette = None
- self._mouse_detector = MouseSpeedDetector(self, 200, 5)
- self._mouse_detector.connect('motion-slow', self.__mouse_slow_cb)
- self.modify_base(gtk.STATE_NORMAL, bg_color.get_gdk_color())
-
- self.add_events(gtk.gdk.POINTER_MOTION_MASK | \
- gtk.gdk.BUTTON_PRESS_MASK | \
- gtk.gdk.BUTTON_RELEASE_MASK | \
- gtk.gdk.LEAVE_NOTIFY_MASK)
-
- self.connect('event-after', self.__event_after_cb)
- self.connect('button-press-event', self.__button_press_cb)
- self.motion_notify_id = self.connect('motion-notify-event', \
- self.__motion_notify_cb)
- self.connect('visibility-notify-event', self.__visibility_notify_cb)
- self.connect('leave-notify-event', self.__leave_notify_event_cb)
-
- def __leave_notify_event_cb(self, widget, event):
- self._mouse_detector.stop()
-
- def __button_press_cb(self, widget, event):
- if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
- # To disable the standard textview popup
- return True
-
- # Links can be activated by clicking.
- def __event_after_cb(self, widget, event):
- if event.type != gtk.gdk.BUTTON_RELEASE:
- return False
-
- x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
- int(event.x), int(event.y))
- iter_tags = self.get_iter_at_location(x, y)
-
- for tag in iter_tags.get_tags():
- url = tag.get_data('url')
- if url is not None:
- if event.button == 3:
- palette = tag.get_data('palette')
- xw, yw = self.get_toplevel().get_pointer()
- palette.move(int(xw), int(yw))
- palette.popup()
- else:
- self._show_via_journal(url)
- break
-
- return False
-
- def check_url_hovering(self, x, y):
- # Looks at all tags covering the position (x, y) in the text view,
- # and if one of them is a link return True
-
- hovering = False
- # When check on_slow_mouse event, the position can be out
- # of the widget and return negative values.
- if x < 0 or y < 0:
- return hovering
-
- self.palette = None
- iter_tags = self.get_iter_at_location(x, y)
-
- tags = iter_tags.get_tags()
- for tag in tags:
- url = tag.get_data('url')
- self.palette = tag.get_data('palette')
- if url is not None:
- hovering = True
- break
- return hovering
-
- def set_cursor_if_appropriate(self, x, y):
- # Looks at all tags covering the position (x, y) in the text view,
- # and if one of them is a link, change the cursor to the "hands" cursor
-
- hovering_over_link = self.check_url_hovering(x, y)
- win = self.get_window(gtk.TEXT_WINDOW_TEXT)
- if hovering_over_link:
- win.set_cursor(self.hand_cursor)
- self._mouse_detector.start()
- else:
- win.set_cursor(None)
- self._mouse_detector.stop()
-
- def __mouse_slow_cb(self, widget):
- x, y = self.get_pointer()
- hovering_over_link = self.check_url_hovering(x, y)
- if hovering_over_link:
- if self.palette is not None:
- xw, yw = self.get_toplevel().get_pointer()
- self.palette.move(xw, yw)
- self.palette.popup()
- self._mouse_detector.stop()
- else:
- if self.palette is not None:
- self.palette.popdown()
-
- # Update the cursor image if the pointer moved.
- def __motion_notify_cb(self, widget, event):
- x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
- int(event.x), int(event.y))
- self.set_cursor_if_appropriate(x, y)
- self.window.get_pointer()
- return False
-
- def __visibility_notify_cb(self, widget, event):
- # Also update the cursor image if the window becomes visible
- # (e.g. when a window covering it got iconified).
-
- wx, wy, __ = self.window.get_pointer()
- bx, by = self.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, wx, wy)
- self.set_cursor_if_appropriate(bx, by)
- return False
-
- def __palette_mouse_enter_cb(self, widget, event):
- self.handler_block(self.motion_notify_id)
-
- def __palette_mouse_leave_cb(self, widget, event):
- self.handler_unblock(self.motion_notify_id)
-
- def add_text(self, text):
- buf = self.get_buffer()
-
- if not self._empty:
- buf.insert(self.iter_text, '\n')
-
- words = text.split()
- for word in words:
- buf.insert(self.iter_text, word)
- buf.insert(self.iter_text, ' ')
-
- self._empty = False
-
-
-class ColorLabel(gtk.Label):
-
- def __init__(self, text, color=None):
- self._color = color
- if self._color is not None:
- text = '<span foreground="%s">' % self._color.get_html() +\
- text + '</span>'
- gtk.Label.__init__(self)
- self.set_use_markup(True)
- self.set_markup(text)
- self.props.selectable = True
-
-
-class ChatBox(gtk.ScrolledWindow):
-
+class ChatBox(hippo.CanvasScrollbars):
def __init__(self):
- gtk.ScrolledWindow.__init__(self)
+ hippo.CanvasScrollbars.__init__(self)
self.owner = presenceservice.get_instance().get_owner()
@@ -217,17 +54,15 @@ class ChatBox(gtk.ScrolledWindow):
self._last_msg = None
self._chat_log = ''
- self._conversation = gtk.VBox()
- self._conversation.set_homogeneous(False)
- self._conversation.props.spacing = style.LINE_WIDTH
- self._conversation.props.border_width = style.LINE_WIDTH
- evbox = gtk.EventBox()
- evbox.modify_bg(gtk.STATE_NORMAL, style.COLOR_WHITE.get_gdk_color())
- evbox.add(self._conversation)
-
- self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
- self.add_with_viewport(evbox)
- vadj = self.get_vadjustment()
+ self._conversation = hippo.CanvasBox(
+ spacing=0,
+ background_color=COLOR_WHITE.get_int())
+
+ self.set_policy(hippo.ORIENTATION_HORIZONTAL,
+ hippo.SCROLLBAR_NEVER)
+ self.set_root(self._conversation)
+
+ vadj = self.props.widget.get_vadjustment()
vadj.connect('changed', self._scroll_changed_cb)
vadj.connect('value-changed', self._scroll_value_changed_cb)
@@ -245,12 +80,17 @@ class ChatBox(gtk.ScrolledWindow):
False: show what buddy said
True: show what buddy did
+ hippo layout:
.------------- rb ---------------.
- | +name_vbox+ +----align-----+ |
+ | +name_vbox+ +----msg_vbox----+ |
| | | | | |
- | | nick: | | +--message---+ | |
- | | | | | text | | |
+ | | nick: | | +--msg_hbox--+ | |
+ | | | | | text | | |
| +---------+ | +------------+ | |
+ | | | |
+ | | +--msg_hbox--+ | |
+ | | | text | url | | |
+ | | +------------+ | |
| +----------------+ |
`--------------------------------'
"""
@@ -270,15 +110,16 @@ class ChatBox(gtk.ScrolledWindow):
color_stroke_html, color_fill_html = ('#000000', '#888888')
# Select text color based on fill color:
- color_fill_rgba = style.Color(color_fill_html).get_rgba()
+ color_fill_rgba = Color(color_fill_html).get_rgba()
color_fill_gray = (color_fill_rgba[0] + color_fill_rgba[1] +
color_fill_rgba[2]) / 3
- color_stroke = style.Color(color_stroke_html)
- color_fill = style.Color(color_fill_html)
+ color_stroke = Color(color_stroke_html).get_int()
+ color_fill = Color(color_fill_html).get_int()
+
if color_fill_gray < 0.5:
- text_color = style.COLOR_WHITE
+ text_color = COLOR_WHITE.get_int()
else:
- text_color = style.COLOR_BLACK
+ text_color = COLOR_BLACK.get_int()
self._add_log(nick, color, text, status_message)
@@ -298,35 +139,82 @@ class ChatBox(gtk.ScrolledWindow):
if not new_msg:
rb = self._last_msg
- self._last_msg.add_text(text)
+ msg_vbox = rb.get_children()[1]
+ msg_hbox = hippo.CanvasBox(
+ orientation=hippo.ORIENTATION_HORIZONTAL)
+ msg_vbox.append(msg_hbox)
else:
- rb = RoundBox()
- screen_width = gtk.gdk.screen_width()
- # keep space to the scrollbar
- rb.set_size_request(screen_width - 50, -1)
- rb.props.border_width = style.DEFAULT_PADDING
- rb.props.spacing = style.DEFAULT_SPACING
- rb.background_color = color_fill
- rb.border_color = color_stroke
- self._last_msg_sender = buddy
+ rb = CanvasRoundBox(background_color=color_fill,
+ border_color=color_stroke,
+ padding=4)
+ rb.props.border_color = color_stroke # Bug #3742
self._last_msg = rb
+ self._last_msg_sender = buddy
if not status_message:
- name = ColorLabel(text=nick + ':', color=text_color)
- name_vbox = gtk.VBox()
- name_vbox.pack_start(name, False, False)
- rb.pack_start(name_vbox, False, False)
- message = TextBox(text_color, color_fill, lang_rtl)
- vbox = gtk.VBox()
- vbox.pack_start(message, True, True)
- rb.pack_start(vbox, True, True)
- self._last_msg = message
- self._conversation.pack_start(rb, False, False)
- message.add_text(text)
- self._conversation.show_all()
+ name = hippo.CanvasText(text=nick + ': ',
+ color=text_color)
+ name_vbox = hippo.CanvasBox(
+ orientation=hippo.ORIENTATION_VERTICAL)
+ name_vbox.append(name)
+ rb.append(name_vbox)
+ msg_vbox = hippo.CanvasBox(
+ orientation=hippo.ORIENTATION_VERTICAL)
+ rb.append(msg_vbox)
+ msg_hbox = hippo.CanvasBox(
+ orientation=hippo.ORIENTATION_HORIZONTAL)
+ msg_vbox.append(msg_hbox)
if status_message:
self._last_msg_sender = None
+ match = URL_REGEXP.match(text)
+ while match:
+ # there is a URL in the text
+ starttext = text[:match.start()]
+ if starttext:
+ message = hippo.CanvasText(
+ text=starttext,
+ size_mode=hippo.CANVAS_SIZE_WRAP_WORD,
+ color=text_color,
+ xalign=hippo.ALIGNMENT_START)
+ msg_hbox.append(message)
+ url = text[match.start():match.end()]
+
+ message = CanvasLink(
+ text=url,
+ color=text_color)
+ attrs = pango.AttrList()
+ attrs.insert(pango.AttrUnderline(pango.UNDERLINE_SINGLE, 0, 32767))
+ message.set_property("attributes", attrs)
+ message.connect('activated', self._link_activated_cb)
+
+ # call interior magic which should mean just:
+ # CanvasInvoker().parent = message
+ CanvasInvoker(message)
+
+ msg_hbox.append(message)
+ text = text[match.end():]
+ match = URL_REGEXP.search(text)
+
+ if text:
+ message = hippo.CanvasText(
+ text=text,
+ size_mode=hippo.CANVAS_SIZE_WRAP_WORD,
+ color=text_color,
+ xalign=hippo.ALIGNMENT_START)
+ msg_hbox.append(message)
+
+ # Order of boxes for RTL languages:
+ if lang_rtl:
+ msg_hbox.reverse()
+ if new_msg:
+ rb.reverse()
+
+ if new_msg:
+ box = hippo.CanvasBox(padding=2)
+ box.append(rb)
+ self._conversation.append(box)
+
def _scroll_value_changed_cb(self, adj, scroll=None):
"""Turn auto scrolling on or off.
If the user scrolled up, turn it off.
@@ -394,6 +282,58 @@ class ChatBox(gtk.ScrolledWindow):
nick, color, status_message, text)
+class CanvasLink(hippo.CanvasLink):
+ def __init__(self, **kwargs):
+ hippo.CanvasLink.__init__(self, **kwargs)
+
+ def create_palette(self):
+ return URLMenu(self.props.text)
+
+
+class URLMenu(Palette):
+ def __init__(self, url):
+ Palette.__init__(self, url)
+
+ self.url = url_check_protocol(url)
+
+ menu_item = MenuItem(_('Copy to Clipboard'), 'edit-copy')
+ menu_item.connect('activate', self._copy_to_clipboard_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ def create_palette(self):
+ pass
+
+ def _copy_to_clipboard_cb(self, menuitem):
+ logger.debug('Copy %s to clipboard', self.url)
+ clipboard = gtk.clipboard_get()
+ targets = [("text/uri-list", 0, 0),
+ ("UTF8_STRING", 0, 1)]
+
+ if not clipboard.set_with_data(targets,
+ self._clipboard_data_get_cb,
+ self._clipboard_clear_cb,
+ (self.url)):
+ logger.error('GtkClipboard.set_with_data failed!')
+ else:
+ self.owns_clipboard = True
+
+ def _clipboard_data_get_cb(self, clipboard, selection, info, data):
+ logger.debug('_clipboard_data_get_cb data=%s target=%s', data,
+ selection.target)
+ if selection.target in ['text/uri-list']:
+ if not selection.set_uris([data]):
+ logger.debug('failed to set_uris')
+ else:
+ logger.debug('not uri')
+ if not selection.set_text(data):
+ logger.debug('failed to set_text')
+
+ def _clipboard_clear_cb(self, clipboard, data):
+ logger.debug('clipboard_clear_cb')
+ self.owns_clipboard = False
+
+
def url_check_protocol(url):
"""Check that the url has a protocol, otherwise prepend https://
diff --git a/espeak_cmd.py b/espeak_cmd.py
index f074207..1f50bbf 100644
--- a/espeak_cmd.py
+++ b/espeak_cmd.py
@@ -25,7 +25,6 @@ import espeak
PITCH_MAX = 99
RATE_MAX = 99
-
class AudioGrabCmd(espeak.BaseAudioGrab):
def speak(self, status, text):
self.make_pipeline('filesrc name=file-source')
@@ -46,7 +45,6 @@ class AudioGrabCmd(espeak.BaseAudioGrab):
# play
self.restart_sound_device()
-
def voices():
out = []
result = subprocess.Popen(["espeak", "--voices"], stdout=subprocess.PIPE) \
@@ -57,7 +55,7 @@ def voices():
if not m:
continue
language, gender, name, stuff = m.groups()
- if stuff.startswith('mb/'): # or \
+ if stuff.startswith('mb/'): #or \
#name in ('en-rhotic','english_rp','english_wmids'):
# these voices don't produce sound
continue
diff --git a/espeak_gst.py b/espeak_gst.py
index 4da4f9d..a492cf6 100644
--- a/espeak_gst.py
+++ b/espeak_gst.py
@@ -23,7 +23,6 @@ import espeak
PITCH_MAX = 200
RATE_MAX = 200
-
class AudioGrabGst(espeak.BaseAudioGrab):
def speak(self, status, text):
# XXX workaround for http://bugs.sugarlabs.org/ticket/1801
@@ -47,7 +46,6 @@ class AudioGrabGst(espeak.BaseAudioGrab):
self.restart_sound_device()
-
def voices():
out = []
diff --git a/eye.py b/eye.py
index 7b78aad..954e68a 100644
--- a/eye.py
+++ b/eye.py
@@ -12,12 +12,12 @@
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
-#
+#
# Speak.activity 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 Speak.activity. If not, see <http://www.gnu.org/licenses/>.
@@ -115,12 +115,8 @@ class Eye(gtk.DrawingArea):
self.context = widget.window.cairo_create()
#self.context.set_antialias(cairo.ANTIALIAS_NONE)
- #set a clip region for the expose event.
- #This reduces redrawing work (and time)
- self.context.rectangle(event.area.x,
- event.area.y,
- event.area.width,
- event.area.height)
+ #set a clip region for the expose event. This reduces redrawing work (and time)
+ self.context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
self.context.clip()
# background
@@ -129,21 +125,13 @@ class Eye(gtk.DrawingArea):
self.context.fill()
# eye ball
- self.context.arc(bounds.width / 2,
- bounds.height / 2,
- eyeSize / 2 - outlineWidth / 2,
- 0,
- 360)
+ self.context.arc(bounds.width / 2, bounds.height / 2, eyeSize / 2 - outlineWidth / 2, 0, 360)
self.context.set_source_rgb(1, 1, 1)
self.context.fill()
# outline
self.context.set_line_width(outlineWidth)
- self.context.arc(bounds.width / 2,
- bounds.height / 2,
- eyeSize / 2 - outlineWidth / 2,
- 0,
- 360)
+ self.context.arc(bounds.width / 2, bounds.height / 2, eyeSize / 2 - outlineWidth / 2, 0, 360)
self.context.set_source_rgb(0, 0, 0)
self.context.stroke()
diff --git a/face.py b/face.py
index b7e3372..a3364b9 100644
--- a/face.py
+++ b/face.py
@@ -7,22 +7,22 @@
#
# Parts of Speak.activity are based on code from Measure.activity
# Copyright (C) 2007 Arjun Sarwal - arjun@laptop.org
-#
+#
# Speak.activity 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 3 of the License, or
# (at your option) any later version.
-#
+#
# Speak.activity 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 Speak.activity. If not, see <http://www.gnu.org/licenses/>.
-import logging
+import logging
import gtk
import cjson
@@ -40,7 +40,6 @@ logger = logging.getLogger('speak')
FACE_PAD = 2
-
class Status:
def __init__(self):
self.voice = voice.defaultVoice()
@@ -51,26 +50,26 @@ class Status:
self.mouth = mouth.Mouth
def serialize(self):
- eyes = {eye.Eye: 1,
- glasses.Glasses: 2}
- mouths = {mouth.Mouth: 1,
- fft_mouth.FFTMouth: 2,
- waveform_mouth.WaveformMouth: 3}
+ eyes = { eye.Eye : 1,
+ glasses.Glasses : 2 }
+ mouths = { mouth.Mouth : 1,
+ fft_mouth.FFTMouth : 2,
+ waveform_mouth.WaveformMouth : 3 }
return cjson.encode({
- 'voice': {'language': self.voice.language,
- 'name': self.voice.name},
- 'pitch': self.pitch,
- 'rate': self.rate,
- 'eyes': [eyes[i] for i in self.eyes],
- 'mouth': mouths[self.mouth]})
+ 'voice' : { 'language' : self.voice.language,
+ 'name' : self.voice.name },
+ 'pitch' : self.pitch,
+ 'rate' : self.rate,
+ 'eyes' : [eyes[i] for i in self.eyes],
+ 'mouth' : mouths[self.mouth] })
def deserialize(self, buf):
- eyes = {1: eye.Eye,
- 2: glasses.Glasses}
- mouths = {1: mouth.Mouth,
- 2: fft_mouth.FFTMouth,
- 3: waveform_mouth.WaveformMouth}
+ eyes = { 1: eye.Eye,
+ 2: glasses.Glasses }
+ mouths = { 1: mouth.Mouth,
+ 2: fft_mouth.FFTMouth,
+ 3: waveform_mouth.WaveformMouth }
data = cjson.decode(buf)
self.voice = voice.Voice(data['voice']['language'],
@@ -91,7 +90,6 @@ class Status:
new.mouth = self.mouth
return new
-
class View(gtk.EventBox):
def __init__(self, fill_color=style.COLOR_BUTTON_GREY):
gtk.EventBox.__init__(self)
@@ -144,7 +142,7 @@ class View(gtk.EventBox):
x, y = pos
map(lambda e, x=x, y=y: e.look_at(x, y), self._eyes)
- def update(self, status=None):
+ def update(self, status = None):
if not status:
status = self.status
else:
@@ -171,8 +169,7 @@ class View(gtk.EventBox):
self._mouth.show()
self._mouthbox.add(self._mouth)
- # enable mouse move events so we can track
- # the eyes while the mouse is over the mouth
+ # enable mouse move events so we can track the eyes while the mouse is over the mouth
#self._mouth.add_events(gtk.gdk.POINTER_MOTION_MASK)
def set_voice(self, voice):
@@ -191,5 +188,4 @@ class View(gtk.EventBox):
self._audio.stop_sound_device()
def _size_allocate_cb(self, widget, allocation):
- self._mouthbox.set_size_request(-1,
- int(allocation.height / 2.5))
+ self._mouthbox.set_size_request(-1, int(allocation.height/2.5))
diff --git a/fft_mouth.py b/fft_mouth.py
index 8200ef6..53bcadf 100644
--- a/fft_mouth.py
+++ b/fft_mouth.py
@@ -7,17 +7,17 @@
#
# Parts of Speak.activity are based on code from Measure.activity
# Copyright (C) 2007 Arjun Sarwal - arjun@laptop.org
-#
+#
# Speak.activity 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 3 of the License, or
# (at your option) any later version.
-#
+#
# Speak.activity 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 Speak.activity. If not, see <http://www.gnu.org/licenses/>.
@@ -33,69 +33,66 @@ except:
from Numeric import ceil
from FFT import *
-
class FFTMouth(Mouth):
def __init__(self, audioSource, fill_color):
Mouth.__init__(self, audioSource, fill_color)
-
+
self.peaks = []
self.y_mag = 1.7
- self.freq_range = 70
+ self.freq_range=70
self.draw_interval = 1
self.num_of_points = 105
- self.stop = False
+ self.stop=False
- #constant to multiply with self.param2 while scaling values
- self.y_mag_bias_multiplier = 1
+ self.y_mag_bias_multiplier = 1 #constant to multiply with self.param2 while scaling values
self.fftx = []
self.scaleX = "10"
self.scaleY = "10"
+
def processBuffer(self, bounds):
- self.param1 = bounds.height / 65536.0
- self.param2 = bounds.height / 2.0
+ self.param1 = bounds.height/65536.0
+ self.param2 = bounds.height/2.0
- if(self.stop == False):
+ if(self.stop==False):
Fs = 48000
- nfft = 65536
- self.newest_buffer = self.newest_buffer[0:256]
- self.fftx = fft(self.newest_buffer, 256, -1)
+ nfft= 65536
+ self.newest_buffer=self.newest_buffer[0:256]
+ self.fftx = fft(self.newest_buffer, 256,-1)
- self.fftx = self.fftx[0:self.freq_range * 2]
- self.draw_interval = bounds.width / (self.freq_range * 2.)
+ self.fftx=self.fftx[0:self.freq_range*2]
+ self.draw_interval=bounds.width/(self.freq_range*2.)
- NumUniquePts = ceil((nfft + 1) / 2)
- self.buffers = abs(self.fftx) * 0.02
- self.y_mag_bias_multiplier = 0.1
+ NumUniquePts = ceil((nfft+1)/2)
+ self.buffers=abs(self.fftx)*0.02
+ self.y_mag_bias_multiplier=0.1
self.scaleX = "hz"
self.scaleY = ""
- if(len(self.buffers) == 0):
+ if(len(self.buffers)==0):
return False
# Scaling the values
val = []
for i in self.buffers:
- temp_val_float = float(self.param1 * i * self.y_mag) +\
- self.y_mag_bias_multiplier * self.param2
+ temp_val_float = float(self.param1*i*self.y_mag) + self.y_mag_bias_multiplier * self.param2
if(temp_val_float >= bounds.height):
- temp_val_float = bounds.height - 25
+ temp_val_float = bounds.height-25
if(temp_val_float <= 0):
temp_val_float = 25
- val.append(temp_val_float)
+ val.append( temp_val_float )
self.peaks = val
def expose(self, widget, event):
- """This function is the "expose" event
- handler and does all the drawing."""
+ """This function is the "expose" event handler and does all the drawing."""
bounds = self.get_allocation()
@@ -105,32 +102,26 @@ class FFTMouth(Mouth):
self.context = widget.window.cairo_create()
self.context.set_antialias(cairo.ANTIALIAS_NONE)
- #set a clip region for the expose event.
- #This reduces redrawing work (and time)
- self.context.rectangle(event.area.x,
- event.area.y,
- event.area.width,
- event.area.height)
+ #set a clip region for the expose event. This reduces redrawing work (and time)
+ self.context.rectangle(event.area.x, event.area.y,event.area.width, event.area.height)
self.context.clip()
# background
self.context.set_source_rgba(*self.fill_color.get_rgba())
- self.context.rectangle(0, 0, bounds.width, bounds.height)
+ self.context.rectangle(0,0, bounds.width,bounds.height)
self.context.fill()
# Draw the waveform
- self.context.set_line_width(min(bounds.height / 10.0, 10))
- self.context.set_source_rgb(0, 0, 0)
+ self.context.set_line_width(min(bounds.height/10.0, 10))
+ self.context.set_source_rgb(0,0,0)
count = 0
for peak in self.peaks:
- self.context.line_to(bounds.width / 2 + count,
- bounds.height / 2 - peak)
+ self.context.line_to(bounds.width/2 + count,bounds.height/2 - peak)
count += self.draw_interval
self.context.stroke()
count = 0
for peak in self.peaks:
- self.context.line_to(bounds.width / 2 - count,
- bounds.height / 2 - peak)
+ self.context.line_to(bounds.width/2 - count,bounds.height/2 - peak)
count += self.draw_interval
self.context.stroke()
diff --git a/glasses.py b/glasses.py
index 5ac1f10..399c01a 100644
--- a/glasses.py
+++ b/glasses.py
@@ -7,17 +7,17 @@
#
# Parts of Speak.activity are based on code from Measure.activity
# Copyright (C) 2007 Arjun Sarwal - arjun@laptop.org
-#
+#
# Speak.activity 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 3 of the License, or
# (at your option) any later version.
-#
+#
# Speak.activity 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 Speak.activity. If not, see <http://www.gnu.org/licenses/>.
@@ -46,12 +46,8 @@ class Glasses(Eye):
self.context = widget.window.cairo_create()
#self.context.set_antialias(cairo.ANTIALIAS_NONE)
- #set a clip region for the expose event.
- # This reduces redrawing work (and time)
- self.context.rectangle(event.area.x,
- event.area.y,
- event.area.width,
- event.area.height)
+ #set a clip region for the expose event. This reduces redrawing work (and time)
+ self.context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
self.context.clip()
# background
@@ -67,19 +63,13 @@ class Glasses(Eye):
self.context.curve_to(x1, y2, x1, y2, x1, (y1 + y2) / 2.)
# eye ball
- roundrect(outlineWidth,
- outlineWidth,
- bounds.width - outlineWidth,
- bounds.height - outlineWidth)
+ roundrect(outlineWidth, outlineWidth, bounds.width - outlineWidth, bounds.height - outlineWidth)
self.context.set_source_rgb(1, 1, 1)
self.context.fill()
# outline
self.context.set_line_width(outlineWidth)
- roundrect(outlineWidth,
- outlineWidth,
- bounds.width - outlineWidth,
- bounds.height - outlineWidth)
+ roundrect(outlineWidth, outlineWidth, bounds.width - outlineWidth, bounds.height - outlineWidth)
#roundrect(0,0, bounds.width,bounds.height)
self.context.set_source_rgb(0, 0, 0)
self.context.stroke()
diff --git a/messenger.py b/messenger.py
index eb03bda..14151e5 100644
--- a/messenger.py
+++ b/messenger.py
@@ -28,7 +28,6 @@ SERVICE = 'org.sugarlabs.Speak'
IFACE = SERVICE
PATH = '/org/sugarlabs/Speak'
-
class Messenger(ExportedGObject):
def __init__(self, tube, is_initiator, chat):
ExportedGObject.__init__(self, tube, PATH)
@@ -62,16 +61,10 @@ class Messenger(ExportedGObject):
if not self._entered:
self.me = self._tube.get_unique_name()
- self._tube.add_signal_receiver(self._ping_cb,
- '_ping',
- IFACE,
- path=PATH,
- sender_keyword='sender')
- self._tube.add_signal_receiver(self._post_cb,
- '_post',
- IFACE,
- path=PATH,
- sender_keyword='sender')
+ self._tube.add_signal_receiver(self._ping_cb, '_ping', IFACE, path=PATH,
+ sender_keyword='sender')
+ self._tube.add_signal_receiver(self._post_cb, '_post', IFACE, path=PATH,
+ sender_keyword='sender')
if not self.is_initiator:
self._ping(self.chat.me.status.serialize())
@@ -104,8 +97,7 @@ class Messenger(ExportedGObject):
tp_handle = self._tube.bus_name_to_handle[sender]
buddy = self._tube.get_buddy(tp_handle)
- if not buddy:
- return
+ if not buddy: return
self._buddies[tp_handle] = buddy
logger.debug('ping received from %s(%s) status=%s' \
@@ -124,7 +116,7 @@ class Messenger(ExportedGObject):
tp_handle = self._tube.bus_name_to_handle[sender]
buddy = self._buddies[tp_handle]
- logger.debug('message received from %s(%s): %s'
+ logger.debug('message received from %s(%s): %s'
% (sender, buddy.props.nick, text))
self.chat.post(buddy, face.Status().deserialize(sender_status), text)
diff --git a/mouth.py b/mouth.py
index 4d88feb..a72312a 100644
--- a/mouth.py
+++ b/mouth.py
@@ -7,17 +7,17 @@
#
# Parts of Speak.activity are based on code from Measure.activity
# Copyright (C) 2007 Arjun Sarwal - arjun@laptop.org
-#
+#
# Speak.activity 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 3 of the License, or
# (at your option) any later version.
-#
+#
# Speak.activity 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 Speak.activity. If not, see <http://www.gnu.org/licenses/>.
@@ -46,8 +46,7 @@ class Mouth(gtk.DrawingArea):
if len(buf) < 28:
self.newest_buffer = []
else:
- self.newest_buffer = list(unpack(str(int(len(buf)) / 2) + 'h',
- buf))
+ self.newest_buffer = list(unpack(str(int(len(buf)) / 2) + 'h', buf))
self.main_buffers += self.newest_buffer
if(len(self.main_buffers) > self.buffer_size):
del self.main_buffers[0:(len(self.main_buffers) - \
@@ -60,12 +59,10 @@ class Mouth(gtk.DrawingArea):
if len(self.main_buffers) == 0 or len(self.newest_buffer) == 0:
self.volume = 0
else:
- self.volume = numpy.core.max(self.main_buffers) # -\
- # numpy.core.min(self.main_buffers)
+ self.volume = numpy.core.max(self.main_buffers) # - numpy.core.min(self.main_buffers)
def expose(self, widget, event):
- """This function is the "expose" event
- handler and does all the drawing."""
+ """This function is the "expose" event handler and does all the drawing."""
bounds = self.get_allocation()
self.processBuffer(bounds)
@@ -74,12 +71,8 @@ class Mouth(gtk.DrawingArea):
self.context = widget.window.cairo_create()
self.context.set_antialias(cairo.ANTIALIAS_NONE)
- # set a clip region for the expose event.
- # This reduces redrawing work (and time)
- self.context.rectangle(event.area.x,
- event.area.y,
- event.area.width,
- event.area.height)
+ #set a clip region for the expose event. This reduces redrawing work (and time)
+ self.context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
self.context.clip()
# background
@@ -90,7 +83,7 @@ class Mouth(gtk.DrawingArea):
# Draw the mouth
volume = self.volume / 30000.
mouthH = volume * bounds.height
- mouthW = volume ** 2 * (bounds.width / 2.) + bounds.width / 2.
+ mouthW = volume**2 * (bounds.width / 2.) + bounds.width / 2.
# T
# L R
# B
diff --git a/po/bi.po b/po/bi.po
index ad2b025..9b596e4 100644
--- a/po/bi.po
+++ b/po/bi.po
@@ -39,7 +39,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2011-08-04 13:41+0000\n"
+"POT-Creation-Date: 2012-06-07 00:36-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -49,100 +49,100 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Translate Toolkit 1.3.0\n"
-#: activity.py:137
+#: activity/activity.info:2
+msgid "Speak"
+msgstr ""
+
+#: activity.py:135
msgid "Type something to hear it"
msgstr ""
-#: activity.py:144
+#: activity.py:142
msgid "Ask robot any question"
msgstr ""
-#: activity.py:152
+#: activity.py:150
msgid "Voice chat"
msgstr ""
-#: activity.py:164
+#: activity.py:159
msgid "Voice"
msgstr ""
-#: activity.py:170
+#: activity.py:165
msgid "Face"
msgstr ""
-#: activity.py:199
+#: activity.py:194
#, python-format
msgid "Hello %s. Please Type something."
msgstr ""
-#: activity.py:263
+#: activity.py:260
msgid "Pitch:"
msgstr ""
-#: activity.py:276
+#: activity.py:273
msgid "Rate:"
msgstr ""
-#: activity.py:284
+#: activity.py:281
msgid "pitch adjusted"
msgstr ""
-#: activity.py:288
+#: activity.py:285
msgid "rate adjusted"
msgstr ""
-#: activity.py:294
+#: activity.py:291
msgid "Simple"
msgstr ""
-#: activity.py:295
+#: activity.py:292
msgid "Waveform"
msgstr ""
-#: activity.py:296
+#: activity.py:293
msgid "Frequency"
msgstr ""
-#: activity.py:301
+#: activity.py:298
msgid "Mouth:"
msgstr ""
-#: activity.py:305
+#: activity.py:302
msgid "Round"
msgstr ""
-#: activity.py:306
+#: activity.py:303
msgid "Glasses"
msgstr ""
-#: activity.py:311
+#: activity.py:308
msgid "Eyes:"
msgstr ""
-#: activity.py:322
+#: activity.py:319
msgid "Eyes number:"
msgstr ""
-#: activity.py:334
+#: activity.py:331
msgid "mouth changed"
msgstr ""
-#: activity.py:346
+#: activity.py:343
msgid "eyes changed"
msgstr ""
-#: activity.py:445
+#: activity.py:442
#, python-format
-msgid "Sorry, I can't speak %s, let's speak %s instead."
+msgid "Sorry, I can't speak %(old_voice)s, let's talk %(new_voice)s instead."
msgstr ""
#: activity.py:473
msgid "You are in off-line mode, share and invite someone."
msgstr ""
-#: activity/activity.info:2
-msgid "Speak"
-msgstr ""
-
#: brain.py:33 voice.py:67
msgid "Spanish"
msgstr ""
@@ -174,35 +174,39 @@ msgstr ""
msgid "Copy to Clipboard"
msgstr ""
-#: toolkit/activity_widgets.py:83
+#: toolkit/activity_widgets.py:84
msgid "Stop"
msgstr ""
-#: toolkit/activity_widgets.py:95
+#: toolkit/activity_widgets.py:96
msgid "Undo"
msgstr ""
-#: toolkit/activity_widgets.py:103
+#: toolkit/activity_widgets.py:104
msgid "Redo"
msgstr ""
-#: toolkit/activity_widgets.py:110
+#: toolkit/activity_widgets.py:111
msgid "Copy"
msgstr ""
-#: toolkit/activity_widgets.py:117
+#: toolkit/activity_widgets.py:118
msgid "Paste"
msgstr ""
-#: toolkit/activity_widgets.py:127
+#: toolkit/activity_widgets.py:128
msgid "Private"
msgstr ""
-#: toolkit/activity_widgets.py:134
+#: toolkit/activity_widgets.py:135
msgid "My Neighborhood"
msgstr ""
-#: toolkit/activity_widgets.py:322
+#: toolkit/activity_widgets.py:213
+msgid "Description"
+msgstr ""
+
+#: toolkit/activity_widgets.py:393
msgid "Activity"
msgstr ""
diff --git a/po/da.po b/po/da.po
index eaff560..4288fc0 100644
--- a/po/da.po
+++ b/po/da.po
@@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-06-30 00:32-0400\n"
-"PO-Revision-Date: 2012-07-06 06:29+0200\n"
+"POT-Creation-Date: 2012-06-07 00:36-0400\n"
+"PO-Revision-Date: 2012-06-08 04:32+0200\n"
"Last-Translator: Aputsiaq Niels <aj@isit.gl>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: da\n"
@@ -41,92 +41,92 @@ msgstr "Stemme"
msgid "Face"
msgstr "Ansigt"
-#: activity.py:196
+#: activity.py:194
#, python-format
#, python-format,
msgid "Hello %s. Please Type something."
msgstr "Hej %s. Indtast venligst noget."
-#: activity.py:262
+#: activity.py:260
msgid "Pitch:"
msgstr "Tonehøjde:"
-#: activity.py:275
+#: activity.py:273
msgid "Rate:"
msgstr "Tonerate:"
-#: activity.py:283
+#: activity.py:281
msgid "pitch adjusted"
msgstr "tonehøjde tilpasset"
-#: activity.py:287
+#: activity.py:285
msgid "rate adjusted"
msgstr "tonerate tilpasset"
-#: activity.py:293
+#: activity.py:291
msgid "Simple"
msgstr "Simpel"
-#: activity.py:295
+#: activity.py:292
msgid "Waveform"
msgstr "Bølgeform"
-#: activity.py:296
+#: activity.py:293
msgid "Frequency"
msgstr "Frekvens"
-#: activity.py:301
+#: activity.py:298
msgid "Mouth:"
msgstr "Mund:"
-#: activity.py:305
+#: activity.py:302
msgid "Round"
msgstr "Rund"
-#: activity.py:306
+#: activity.py:303
msgid "Glasses"
msgstr "Briller"
-#: activity.py:311
+#: activity.py:308
msgid "Eyes:"
msgstr "Øjne:"
-#: activity.py:322
+#: activity.py:319
msgid "Eyes number:"
msgstr "Antal øjne:"
-#: activity.py:334
+#: activity.py:331
msgid "mouth changed"
msgstr "mund ændret"
-#: activity.py:346
+#: activity.py:343
msgid "eyes changed"
msgstr "øjne ændret"
-#: activity.py:446
+#: activity.py:442
#, python-format
msgid "Sorry, I can't speak %(old_voice)s, let's talk %(new_voice)s instead."
msgstr ""
"Beklager, jeg kan ikke tale %(old_voice)s, lad os tale %(new_voice)s i "
"stedet."
-#: activity.py:477
+#: activity.py:473
msgid "You are in off-line mode, share and invite someone."
msgstr "Du er i frakoblet tilstand, del og invitér en person."
-#: brain.py:34 voice.py:69
+#: brain.py:33 voice.py:67
msgid "Spanish"
msgstr "Spansk"
-#: brain.py:38 brain.py:53 brain.py:55 brain.py:65 voice.py:60
+#: brain.py:37 brain.py:52 brain.py:54 brain.py:64 voice.py:58
msgid "English"
msgstr "Engelsk"
-#: brain.py:81
+#: brain.py:80
msgid "Sorry, I can't understand what you are asking about."
msgstr "Beklager, jeg kan ikke forstå hvad du spørger om."
-#: brain.py:105
+#: brain.py:104
msgid ""
"Sorry, there is no free memory to load my brain. Close other activities and "
"try once more."
@@ -134,10 +134,11 @@ msgstr ""
"Beklager, der er ikke fri hukommelse til indlæsning af min hjerne. Luk andre "
"aktiviteter og forsøg en gang mere."
-#: brain.py:126
+#: brain.py:125
#, python-format
-msgid "Hello, I'm a robot \"%s\". "
-msgstr "Hej, jeg er en robot \"%s\". "
+#, python-format,
+msgid "Hello, I'm a robot \"%s\". Please ask me any question."
+msgstr "Hej, jeg er robot \"%s\". Venligst stil mig hvilket som helst spørgsmål."
#: chatbox.py:248
msgid "URL from Chat"
@@ -183,137 +184,132 @@ msgstr "Beskrivelse"
msgid "Activity"
msgstr "Aktivitet"
-#: voice.py:39
+#: voice.py:37
msgid "Brazil"
msgstr "Brasiliansk"
-#: voice.py:40
+#: voice.py:38
msgid "Swedish"
msgstr "Svensk"
-#: voice.py:41
+#: voice.py:39
msgid "Icelandic"
msgstr "Islandsk"
-#: voice.py:42
+#: voice.py:40
msgid "Romanian"
msgstr "Rumænsk"
-#: voice.py:43
+#: voice.py:41
msgid "Swahili"
msgstr "Swahili"
-#: voice.py:44
+#: voice.py:42
msgid "Hindi"
msgstr "Hindi"
-#: voice.py:45
+#: voice.py:43
msgid "Dutch"
msgstr "Hollandsk"
-#: voice.py:46
+#: voice.py:44
msgid "Latin"
msgstr "Latin"
-#: voice.py:47
+#: voice.py:45
msgid "Hungarian"
msgstr "Ungarsk"
-#: voice.py:48
+#: voice.py:46
msgid "Macedonian"
msgstr "Makedonsk"
-#: voice.py:49
+#: voice.py:47
msgid "Welsh"
msgstr "Walisisk"
-#: voice.py:50
+#: voice.py:48
msgid "French"
msgstr "Fransk"
-#: voice.py:51
+#: voice.py:49
msgid "Norwegian"
msgstr "Norsk"
-#: voice.py:52
+#: voice.py:50
msgid "Russian"
msgstr "Russisk"
-#: voice.py:53
+#: voice.py:51
msgid "Afrikaans"
msgstr "Afrikaans"
-#: voice.py:54
+#: voice.py:52
msgid "Finnish"
msgstr "Finsk"
-#: voice.py:55 voice.py:158
+#: voice.py:53 voice.py:157
msgid "Default"
msgstr "Standard"
-#: voice.py:56
+#: voice.py:54
msgid "Cantonese"
msgstr "Kantonesisk"
-#: voice.py:57
+#: voice.py:55
msgid "Scottish"
msgstr "Skotsk"
-#: voice.py:58
+#: voice.py:56
msgid "Greek"
msgstr "Græsk"
-#: voice.py:59
+#: voice.py:57
msgid "Vietnam"
msgstr "Vietnamesisk"
-#: voice.py:61
+#: voice.py:59
msgid "Lancashire"
msgstr "Lancashire"
-#: voice.py:62
+#: voice.py:60
msgid "Italian"
msgstr "Italiensk"
-#: voice.py:63
+#: voice.py:61
msgid "Portugal"
msgstr "Portugisisk"
-#: voice.py:64
+#: voice.py:62
msgid "German"
msgstr "Tysk"
-#: voice.py:65
+#: voice.py:63
msgid "Whisper"
msgstr "Hvisken"
-#: voice.py:66
+#: voice.py:64
msgid "Croatian"
msgstr "Kroatisk"
-#: voice.py:67
+#: voice.py:65
msgid "Czech"
msgstr "Tjekkisk"
-#: voice.py:68
+#: voice.py:66
msgid "Slovak"
msgstr "Slovakisk"
-#: voice.py:70
+#: voice.py:68
msgid "Polish"
msgstr "Polsk"
-#: voice.py:71
+#: voice.py:69
msgid "Esperanto"
msgstr "Esperanto"
#, python-format
#, python-format,
-#~ msgid "Hello, I'm a robot \"%s\". Please ask me any question."
-#~ msgstr "Hej, jeg er robot \"%s\". Venligst stil mig hvilket som helst spørgsmål."
-
-#, python-format
-#, python-format,
#~ msgid "Sorry, I can't speak %s, let's talk %s instead."
#~ msgstr "Beklager, jeg kan ikke tale %s, lad os tale %s i stedet for."
diff --git a/po/de.po b/po/de.po
index 01c16a5..04870c0 100644
--- a/po/de.po
+++ b/po/de.po
@@ -59,8 +59,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-07 00:36-0400\n"
-"PO-Revision-Date: 2012-02-19 12:51+0200\n"
-"Last-Translator: olenz <olaf@lenz.name>\n"
+"PO-Revision-Date: 2012-06-25 21:48+0200\n"
+"Last-Translator: mattthias <matthias@sigxcpu.org>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
@@ -230,7 +230,7 @@ msgstr "Meine Umgebung"
#: toolkit/activity_widgets.py:213
msgid "Description"
-msgstr ""
+msgstr "Beschreibung"
#: toolkit/activity_widgets.py:393
msgid "Activity"
diff --git a/po/en.po b/po/en.po
index fffdd06..cea9732 100644
--- a/po/en.po
+++ b/po/en.po
@@ -42,8 +42,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-06-30 00:32-0400\n"
-"PO-Revision-Date: 2012-07-01 22:45+0200\n"
+"POT-Creation-Date: 2012-06-07 00:36-0400\n"
+"PO-Revision-Date: 2012-06-07 07:35+0200\n"
"Last-Translator: Chris <cjl@laptop.org>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: en\n"
@@ -77,90 +77,90 @@ msgstr "Voice"
msgid "Face"
msgstr "Face"
-#: activity.py:196
+#: activity.py:194
#, python-format
#, python-format,
msgid "Hello %s. Please Type something."
msgstr "Hello %s. Please Type something."
-#: activity.py:262
+#: activity.py:260
msgid "Pitch:"
msgstr "Pitch:"
-#: activity.py:275
+#: activity.py:273
msgid "Rate:"
msgstr "Rate:"
-#: activity.py:283
+#: activity.py:281
msgid "pitch adjusted"
msgstr "pitch adjusted"
-#: activity.py:287
+#: activity.py:285
msgid "rate adjusted"
msgstr "rate adjusted"
-#: activity.py:293
+#: activity.py:291
msgid "Simple"
msgstr "Simple"
-#: activity.py:295
+#: activity.py:292
msgid "Waveform"
msgstr "Waveform"
-#: activity.py:296
+#: activity.py:293
msgid "Frequency"
msgstr "Frequency"
-#: activity.py:301
+#: activity.py:298
msgid "Mouth:"
msgstr "Mouth:"
-#: activity.py:305
+#: activity.py:302
msgid "Round"
msgstr "Round"
-#: activity.py:306
+#: activity.py:303
msgid "Glasses"
msgstr "Glasses"
-#: activity.py:311
+#: activity.py:308
msgid "Eyes:"
msgstr "Eyes:"
-#: activity.py:322
+#: activity.py:319
msgid "Eyes number:"
msgstr "Eyes number:"
-#: activity.py:334
+#: activity.py:331
msgid "mouth changed"
msgstr "mouth changed"
-#: activity.py:346
+#: activity.py:343
msgid "eyes changed"
msgstr "eyes changed"
-#: activity.py:446
+#: activity.py:442
#, python-format
msgid "Sorry, I can't speak %(old_voice)s, let's talk %(new_voice)s instead."
msgstr "Sorry, I can't speak %(old_voice)s, let's talk %(new_voice)s instead."
-#: activity.py:477
+#: activity.py:473
msgid "You are in off-line mode, share and invite someone."
msgstr "You are in off-line mode, share and invite someone."
-#: brain.py:34 voice.py:69
+#: brain.py:33 voice.py:67
msgid "Spanish"
msgstr "Spanish"
-#: brain.py:38 brain.py:53 brain.py:55 brain.py:65 voice.py:60
+#: brain.py:37 brain.py:52 brain.py:54 brain.py:64 voice.py:58
msgid "English"
msgstr "English"
-#: brain.py:81
+#: brain.py:80
msgid "Sorry, I can't understand what you are asking about."
msgstr "Sorry, I can't understand what you are asking about."
-#: brain.py:105
+#: brain.py:104
msgid ""
"Sorry, there is no free memory to load my brain. Close other activities and "
"try once more."
@@ -168,10 +168,11 @@ msgstr ""
"Sorry, there is no free memory to load my brain. Close other activities and "
"try once more."
-#: brain.py:126
+#: brain.py:125
#, python-format
-msgid "Hello, I'm a robot \"%s\". "
-msgstr "Hello, I'm a robot \"%s\". "
+#, python-format,
+msgid "Hello, I'm a robot \"%s\". Please ask me any question."
+msgstr "Hello, I'm a robot \"%s\". Please ask me any question."
#: chatbox.py:248
msgid "URL from Chat"
@@ -217,137 +218,132 @@ msgstr "Description"
msgid "Activity"
msgstr "Activity"
-#: voice.py:39
+#: voice.py:37
msgid "Brazil"
msgstr "Brazil"
-#: voice.py:40
+#: voice.py:38
msgid "Swedish"
msgstr "Swedish"
-#: voice.py:41
+#: voice.py:39
msgid "Icelandic"
msgstr "Icelandic"
-#: voice.py:42
+#: voice.py:40
msgid "Romanian"
msgstr "Romanian"
-#: voice.py:43
+#: voice.py:41
msgid "Swahili"
msgstr "Swahili"
-#: voice.py:44
+#: voice.py:42
msgid "Hindi"
msgstr "Hindi"
-#: voice.py:45
+#: voice.py:43
msgid "Dutch"
msgstr "Dutch"
-#: voice.py:46
+#: voice.py:44
msgid "Latin"
msgstr "Latin"
-#: voice.py:47
+#: voice.py:45
msgid "Hungarian"
msgstr "Hungarian"
-#: voice.py:48
+#: voice.py:46
msgid "Macedonian"
msgstr "Macedonian"
-#: voice.py:49
+#: voice.py:47
msgid "Welsh"
msgstr "Welsh"
-#: voice.py:50
+#: voice.py:48
msgid "French"
msgstr "French"
-#: voice.py:51
+#: voice.py:49
msgid "Norwegian"
msgstr "Norwegian"
-#: voice.py:52
+#: voice.py:50
msgid "Russian"
msgstr "Russian"
-#: voice.py:53
+#: voice.py:51
msgid "Afrikaans"
msgstr "Afrikaans"
-#: voice.py:54
+#: voice.py:52
msgid "Finnish"
msgstr "Finnish"
-#: voice.py:55 voice.py:158
+#: voice.py:53 voice.py:157
msgid "Default"
msgstr "Default"
-#: voice.py:56
+#: voice.py:54
msgid "Cantonese"
msgstr "Cantonese"
-#: voice.py:57
+#: voice.py:55
msgid "Scottish"
msgstr "Scottish"
-#: voice.py:58
+#: voice.py:56
msgid "Greek"
msgstr "Greek"
-#: voice.py:59
+#: voice.py:57
msgid "Vietnam"
msgstr "Vietnam"
-#: voice.py:61
+#: voice.py:59
msgid "Lancashire"
msgstr "Lancashire"
-#: voice.py:62
+#: voice.py:60
msgid "Italian"
msgstr "Italian"
-#: voice.py:63
+#: voice.py:61
msgid "Portugal"
msgstr "Portugal"
-#: voice.py:64
+#: voice.py:62
msgid "German"
msgstr "German"
-#: voice.py:65
+#: voice.py:63
msgid "Whisper"
msgstr "Whisper"
-#: voice.py:66
+#: voice.py:64
msgid "Croatian"
msgstr "Croatian"
-#: voice.py:67
+#: voice.py:65
msgid "Czech"
msgstr "Czech"
-#: voice.py:68
+#: voice.py:66
msgid "Slovak"
msgstr "Slovak"
-#: voice.py:70
+#: voice.py:68
msgid "Polish"
msgstr "Polish"
-#: voice.py:71
+#: voice.py:69
msgid "Esperanto"
msgstr "Esperanto"
#, python-format
#, python-format,
-#~ msgid "Hello, I'm a robot \"%s\". Please ask me any question."
-#~ msgstr "Hello, I'm a robot \"%s\". Please ask me any question."
-
-#, python-format
-#, python-format,
#~ msgid "Sorry, I can't speak %s, let's talk %s instead."
#~ msgstr "Sorry, I can't speak %s, let's talk %s instead."
diff --git a/po/en_GB.po b/po/en_GB.po
index 78f7bd4..5407c50 100644
--- a/po/en_GB.po
+++ b/po/en_GB.po
@@ -26,8 +26,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-07-04 00:32-0400\n"
-"PO-Revision-Date: 2012-07-09 08:08+0200\n"
+"POT-Creation-Date: 2012-06-07 00:36-0400\n"
+"PO-Revision-Date: 2012-06-07 17:18+0200\n"
"Last-Translator: Chris <cjl@laptop.org>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: en_GB\n"
@@ -41,110 +41,110 @@ msgstr ""
msgid "Speak"
msgstr "Speak"
-#: activity.py:162
+#: activity.py:135
msgid "Type something to hear it"
msgstr "Type something to hear it"
-#: activity.py:169
+#: activity.py:142
msgid "Ask robot any question"
msgstr "Ask robot any question"
-#: activity.py:177
+#: activity.py:150
msgid "Voice chat"
msgstr "Voice chat"
-#: activity.py:186
+#: activity.py:159
msgid "Voice"
msgstr "Voice"
-#: activity.py:192
+#: activity.py:165
msgid "Face"
msgstr "Face"
-#: activity.py:270
+#: activity.py:194
#, python-format
#, python-format,
msgid "Hello %s. Please Type something."
msgstr "Hello %s. Please Type something."
-#: activity.py:336
+#: activity.py:260
msgid "Pitch:"
msgstr "Pitch:"
-#: activity.py:349
+#: activity.py:273
msgid "Rate:"
msgstr "Rate:"
-#: activity.py:357
+#: activity.py:281
msgid "pitch adjusted"
msgstr "pitch adjusted"
-#: activity.py:361
+#: activity.py:285
msgid "rate adjusted"
msgstr "rate adjusted"
-#: activity.py:367
+#: activity.py:291
msgid "Simple"
msgstr "Simple"
-#: activity.py:369
+#: activity.py:292
msgid "Waveform"
msgstr "Waveform"
-#: activity.py:370
+#: activity.py:293
msgid "Frequency"
msgstr "Frequency"
-#: activity.py:375
+#: activity.py:298
msgid "Mouth:"
msgstr "Mouth:"
-#: activity.py:379
+#: activity.py:302
msgid "Round"
msgstr "Round"
-#: activity.py:380
+#: activity.py:303
msgid "Glasses"
msgstr "Glasses"
-#: activity.py:385
+#: activity.py:308
msgid "Eyes:"
msgstr "Eyes:"
-#: activity.py:396
+#: activity.py:319
msgid "Eyes number:"
msgstr "Eyes number:"
-#: activity.py:408
+#: activity.py:331
msgid "mouth changed"
msgstr "mouth changed"
-#: activity.py:420
+#: activity.py:343
msgid "eyes changed"
msgstr "eyes changed"
-#: activity.py:520
+#: activity.py:442
#, python-format
msgid "Sorry, I can't speak %(old_voice)s, let's talk %(new_voice)s instead."
msgstr "Sorry, I can't speak %(old_voice)s, let's talk %(new_voice)s instead."
-#: activity.py:551
+#: activity.py:473
msgid "You are in off-line mode, share and invite someone."
msgstr "You are in off-line mode, share and invite someone."
-#: brain.py:34 voice.py:69
+#: brain.py:33 voice.py:67
msgid "Spanish"
msgstr "Spanish"
-#: brain.py:38 brain.py:53 brain.py:55 brain.py:65 voice.py:60
+#: brain.py:37 brain.py:52 brain.py:54 brain.py:64 voice.py:58
msgid "English"
msgstr "English"
-#: brain.py:81
+#: brain.py:80
msgid "Sorry, I can't understand what you are asking about."
msgstr "Sorry, I can't understand what you are asking about."
-#: brain.py:105
+#: brain.py:104
msgid ""
"Sorry, there is no free memory to load my brain. Close other activities and "
"try once more."
@@ -152,10 +152,11 @@ msgstr ""
"Sorry, there is no free memory to load my brain. Close other activities and "
"try once more."
-#: brain.py:126
+#: brain.py:125
#, python-format
-msgid "Hello, I'm a robot \"%s\". "
-msgstr "Hello, I'm a robot \"%s\". "
+#, python-format,
+msgid "Hello, I'm a robot \"%s\". Please ask me any question."
+msgstr "Hello, I'm a robot \"%s\". Please ask me any question."
#: chatbox.py:248
msgid "URL from Chat"
@@ -165,162 +166,166 @@ msgstr "URL from Chat"
msgid "Copy to Clipboard"
msgstr "Copy to Clipboard"
-#: voice.py:39
+#: toolkit/activity_widgets.py:84
+msgid "Stop"
+msgstr "Stop"
+
+#: toolkit/activity_widgets.py:96
+msgid "Undo"
+msgstr "Undo"
+
+#: toolkit/activity_widgets.py:104
+msgid "Redo"
+msgstr "Redo"
+
+#: toolkit/activity_widgets.py:111
+msgid "Copy"
+msgstr "Copy"
+
+#: toolkit/activity_widgets.py:118
+msgid "Paste"
+msgstr "Paste"
+
+#: toolkit/activity_widgets.py:128
+msgid "Private"
+msgstr "Private"
+
+#: toolkit/activity_widgets.py:135
+msgid "My Neighborhood"
+msgstr "My Neighbourhood"
+
+#: toolkit/activity_widgets.py:213
+msgid "Description"
+msgstr "Description"
+
+#: toolkit/activity_widgets.py:393
+msgid "Activity"
+msgstr "Activity"
+
+#: voice.py:37
msgid "Brazil"
msgstr "Brazil"
-#: voice.py:40
+#: voice.py:38
msgid "Swedish"
msgstr "Swedish"
-#: voice.py:41
+#: voice.py:39
msgid "Icelandic"
msgstr "Icelandic"
-#: voice.py:42
+#: voice.py:40
msgid "Romanian"
msgstr "Romanian"
-#: voice.py:43
+#: voice.py:41
msgid "Swahili"
msgstr "Swahili"
-#: voice.py:44
+#: voice.py:42
msgid "Hindi"
msgstr "Hindi"
-#: voice.py:45
+#: voice.py:43
msgid "Dutch"
msgstr "Dutch"
-#: voice.py:46
+#: voice.py:44
msgid "Latin"
msgstr "Latin"
-#: voice.py:47
+#: voice.py:45
msgid "Hungarian"
msgstr "Hungarian"
-#: voice.py:48
+#: voice.py:46
msgid "Macedonian"
msgstr "Macedonian"
-#: voice.py:49
+#: voice.py:47
msgid "Welsh"
msgstr "Welsh"
-#: voice.py:50
+#: voice.py:48
msgid "French"
msgstr "French"
-#: voice.py:51
+#: voice.py:49
msgid "Norwegian"
msgstr "Norwegian"
-#: voice.py:52
+#: voice.py:50
msgid "Russian"
msgstr "Russian"
-#: voice.py:53
+#: voice.py:51
msgid "Afrikaans"
msgstr "Afrikaans"
-#: voice.py:54
+#: voice.py:52
msgid "Finnish"
msgstr "Finnish"
-#: voice.py:55 voice.py:158
+#: voice.py:53 voice.py:157
msgid "Default"
msgstr "Default"
-#: voice.py:56
+#: voice.py:54
msgid "Cantonese"
msgstr "Cantonese"
-#: voice.py:57
+#: voice.py:55
msgid "Scottish"
msgstr "Scottish"
-#: voice.py:58
+#: voice.py:56
msgid "Greek"
msgstr "Greek"
-#: voice.py:59
+#: voice.py:57
msgid "Vietnam"
msgstr "Vietnam"
-#: voice.py:61
+#: voice.py:59
msgid "Lancashire"
msgstr "Lancashire"
-#: voice.py:62
+#: voice.py:60
msgid "Italian"
msgstr "Italian"
-#: voice.py:63
+#: voice.py:61
msgid "Portugal"
msgstr "Portugal"
-#: voice.py:64
+#: voice.py:62
msgid "German"
msgstr "German"
-#: voice.py:65
+#: voice.py:63
msgid "Whisper"
msgstr "Whisper"
-#: voice.py:66
+#: voice.py:64
msgid "Croatian"
msgstr "Croatian"
-#: voice.py:67
+#: voice.py:65
msgid "Czech"
msgstr "Czech"
-#: voice.py:68
+#: voice.py:66
msgid "Slovak"
msgstr "Slovak"
-#: voice.py:70
+#: voice.py:68
msgid "Polish"
msgstr "Polish"
-#: voice.py:71
+#: voice.py:69
msgid "Esperanto"
msgstr "Esperanto"
-#~ msgid "Stop"
-#~ msgstr "Stop"
-
-#~ msgid "Undo"
-#~ msgstr "Undo"
-
-#~ msgid "Redo"
-#~ msgstr "Redo"
-
-#~ msgid "Copy"
-#~ msgstr "Copy"
-
-#~ msgid "Paste"
-#~ msgstr "Paste"
-
-#~ msgid "Private"
-#~ msgstr "Private"
-
-#~ msgid "My Neighborhood"
-#~ msgstr "My Neighbourhood"
-
-#~ msgid "Description"
-#~ msgstr "Description"
-
-#~ msgid "Activity"
-#~ msgstr "Activity"
-
-#, python-format
-#, python-format,
-#~ msgid "Hello, I'm a robot \"%s\". Please ask me any question."
-#~ msgstr "Hello, I'm a robot \"%s\". Please ask me any question."
-
#, python-format
#, python-format,
#~ msgid "Sorry, I can't speak %s, let's talk %s instead."
diff --git a/po/en_US.po b/po/en_US.po
index 2cbd2d1..adeb6d2 100644
--- a/po/en_US.po
+++ b/po/en_US.po
@@ -30,8 +30,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-07-04 00:32-0400\n"
-"PO-Revision-Date: 2012-07-08 08:24+0200\n"
+"POT-Creation-Date: 2012-06-07 00:36-0400\n"
+"PO-Revision-Date: 2012-06-07 17:24+0200\n"
"Last-Translator: Chris <cjl@laptop.org>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: en_US\n"
@@ -45,110 +45,110 @@ msgstr ""
msgid "Speak"
msgstr "Speak"
-#: activity.py:162
+#: activity.py:135
msgid "Type something to hear it"
msgstr "Type something to hear it"
-#: activity.py:169
+#: activity.py:142
msgid "Ask robot any question"
msgstr "Ask robot any question"
-#: activity.py:177
+#: activity.py:150
msgid "Voice chat"
msgstr "Voice chat"
-#: activity.py:186
+#: activity.py:159
msgid "Voice"
msgstr "Voice"
-#: activity.py:192
+#: activity.py:165
msgid "Face"
msgstr "Face"
-#: activity.py:270
+#: activity.py:194
#, python-format
#, python-format,
msgid "Hello %s. Please Type something."
msgstr "Hello %s. Please Type something."
-#: activity.py:336
+#: activity.py:260
msgid "Pitch:"
msgstr "Pitch:"
-#: activity.py:349
+#: activity.py:273
msgid "Rate:"
msgstr "Rate:"
-#: activity.py:357
+#: activity.py:281
msgid "pitch adjusted"
msgstr "pitch adjusted"
-#: activity.py:361
+#: activity.py:285
msgid "rate adjusted"
msgstr "rate adjusted"
-#: activity.py:367
+#: activity.py:291
msgid "Simple"
msgstr "Simple"
-#: activity.py:369
+#: activity.py:292
msgid "Waveform"
msgstr "Waveform"
-#: activity.py:370
+#: activity.py:293
msgid "Frequency"
msgstr "Frequency"
-#: activity.py:375
+#: activity.py:298
msgid "Mouth:"
msgstr "Mouth:"
-#: activity.py:379
+#: activity.py:302
msgid "Round"
msgstr "Round"
-#: activity.py:380
+#: activity.py:303
msgid "Glasses"
msgstr "Glasses"
-#: activity.py:385
+#: activity.py:308
msgid "Eyes:"
msgstr "Eyes:"
-#: activity.py:396
+#: activity.py:319
msgid "Eyes number:"
msgstr "Eyes number:"
-#: activity.py:408
+#: activity.py:331
msgid "mouth changed"
msgstr "mouth changed"
-#: activity.py:420
+#: activity.py:343
msgid "eyes changed"
msgstr "eyes changed"
-#: activity.py:520
+#: activity.py:442
#, python-format
msgid "Sorry, I can't speak %(old_voice)s, let's talk %(new_voice)s instead."
msgstr "Sorry, I can't speak %(old_voice)s, let's talk %(new_voice)s instead."
-#: activity.py:551
+#: activity.py:473
msgid "You are in off-line mode, share and invite someone."
msgstr "You are in off-line mode, share and invite someone."
-#: brain.py:34 voice.py:69
+#: brain.py:33 voice.py:67
msgid "Spanish"
msgstr "Spanish"
-#: brain.py:38 brain.py:53 brain.py:55 brain.py:65 voice.py:60
+#: brain.py:37 brain.py:52 brain.py:54 brain.py:64 voice.py:58
msgid "English"
msgstr "English"
-#: brain.py:81
+#: brain.py:80
msgid "Sorry, I can't understand what you are asking about."
msgstr "Sorry, I can't understand what you are asking about."
-#: brain.py:105
+#: brain.py:104
msgid ""
"Sorry, there is no free memory to load my brain. Close other activities and "
"try once more."
@@ -156,10 +156,11 @@ msgstr ""
"Sorry, there is no free memory to load my brain. Close other activities and "
"try once more."
-#: brain.py:126
+#: brain.py:125
#, python-format
-msgid "Hello, I'm a robot \"%s\". "
-msgstr "Hello, I'm a robot \"%s\". "
+#, python-format,
+msgid "Hello, I'm a robot \"%s\". Please ask me any question."
+msgstr "Hello, I'm a robot \"%s\". Please ask me any question."
#: chatbox.py:248
msgid "URL from Chat"
@@ -169,162 +170,166 @@ msgstr "URL from Chat"
msgid "Copy to Clipboard"
msgstr "Copy to Clipboard"
-#: voice.py:39
+#: toolkit/activity_widgets.py:84
+msgid "Stop"
+msgstr "Stop"
+
+#: toolkit/activity_widgets.py:96
+msgid "Undo"
+msgstr "Undo"
+
+#: toolkit/activity_widgets.py:104
+msgid "Redo"
+msgstr "Redo"
+
+#: toolkit/activity_widgets.py:111
+msgid "Copy"
+msgstr "Copy"
+
+#: toolkit/activity_widgets.py:118
+msgid "Paste"
+msgstr "Paste"
+
+#: toolkit/activity_widgets.py:128
+msgid "Private"
+msgstr "Private"
+
+#: toolkit/activity_widgets.py:135
+msgid "My Neighborhood"
+msgstr "My Neighborhood"
+
+#: toolkit/activity_widgets.py:213
+msgid "Description"
+msgstr "Description"
+
+#: toolkit/activity_widgets.py:393
+msgid "Activity"
+msgstr "Activity"
+
+#: voice.py:37
msgid "Brazil"
msgstr "Brazil"
-#: voice.py:40
+#: voice.py:38
msgid "Swedish"
msgstr "Swedish"
-#: voice.py:41
+#: voice.py:39
msgid "Icelandic"
msgstr "Icelandic"
-#: voice.py:42
+#: voice.py:40
msgid "Romanian"
msgstr "Romanian"
-#: voice.py:43
+#: voice.py:41
msgid "Swahili"
msgstr "Swahili"
-#: voice.py:44
+#: voice.py:42
msgid "Hindi"
msgstr "Hindi"
-#: voice.py:45
+#: voice.py:43
msgid "Dutch"
msgstr "Dutch"
-#: voice.py:46
+#: voice.py:44
msgid "Latin"
msgstr "Latin"
-#: voice.py:47
+#: voice.py:45
msgid "Hungarian"
msgstr "Hungarian"
-#: voice.py:48
+#: voice.py:46
msgid "Macedonian"
msgstr "Macedonian"
-#: voice.py:49
+#: voice.py:47
msgid "Welsh"
msgstr "Welsh"
-#: voice.py:50
+#: voice.py:48
msgid "French"
msgstr "French"
-#: voice.py:51
+#: voice.py:49
msgid "Norwegian"
msgstr "Norwegian"
-#: voice.py:52
+#: voice.py:50
msgid "Russian"
msgstr "Russian"
-#: voice.py:53
+#: voice.py:51
msgid "Afrikaans"
msgstr "Afrikaans"
-#: voice.py:54
+#: voice.py:52
msgid "Finnish"
msgstr "Finnish"
-#: voice.py:55 voice.py:158
+#: voice.py:53 voice.py:157
msgid "Default"
msgstr "Default"
-#: voice.py:56
+#: voice.py:54
msgid "Cantonese"
msgstr "Cantonese"
-#: voice.py:57
+#: voice.py:55
msgid "Scottish"
msgstr "Scottish"
-#: voice.py:58
+#: voice.py:56
msgid "Greek"
msgstr "Greek"
-#: voice.py:59
+#: voice.py:57
msgid "Vietnam"
msgstr "Vietnam"
-#: voice.py:61
+#: voice.py:59
msgid "Lancashire"
msgstr "Lancashire"
-#: voice.py:62
+#: voice.py:60
msgid "Italian"
msgstr "Italian"
-#: voice.py:63
+#: voice.py:61
msgid "Portugal"
msgstr "Portugal"
-#: voice.py:64
+#: voice.py:62
msgid "German"
msgstr "German"
-#: voice.py:65
+#: voice.py:63
msgid "Whisper"
msgstr "Whisper"
-#: voice.py:66
+#: voice.py:64
msgid "Croatian"
msgstr "Croatian"
-#: voice.py:67
+#: voice.py:65
msgid "Czech"
msgstr "Czech"
-#: voice.py:68
+#: voice.py:66
msgid "Slovak"
msgstr "Slovak"
-#: voice.py:70
+#: voice.py:68
msgid "Polish"
msgstr "Polish"
-#: voice.py:71
+#: voice.py:69
msgid "Esperanto"
msgstr "Esperanto"
-#~ msgid "Stop"
-#~ msgstr "Stop"
-
-#~ msgid "Undo"
-#~ msgstr "Undo"
-
-#~ msgid "Redo"
-#~ msgstr "Redo"
-
-#~ msgid "Copy"
-#~ msgstr "Copy"
-
-#~ msgid "Paste"
-#~ msgstr "Paste"
-
-#~ msgid "Private"
-#~ msgstr "Private"
-
-#~ msgid "My Neighborhood"
-#~ msgstr "My Neighborhood"
-
-#~ msgid "Description"
-#~ msgstr "Description"
-
-#~ msgid "Activity"
-#~ msgstr "Activity"
-
-#, python-format
-#, python-format,
-#~ msgid "Hello, I'm a robot \"%s\". Please ask me any question."
-#~ msgstr "Hello, I'm a robot \"%s\". Please ask me any question."
-
#, python-format
#, python-format,
#~ msgid "Sorry, I can't speak %s, let's talk %s instead."
diff --git a/po/fr.po b/po/fr.po
index 98dbb30..fb3f61c 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -43,7 +43,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-07 00:36-0400\n"
-"PO-Revision-Date: 2011-10-29 19:48+0200\n"
+"PO-Revision-Date: 2012-07-14 00:39+0200\n"
"Last-Translator: samy boutayeb <s.boutayeb@free.fr>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: fr\n"
@@ -213,7 +213,7 @@ msgstr "Mon voisinage"
#: toolkit/activity_widgets.py:213
msgid "Description"
-msgstr ""
+msgstr "Description"
#: toolkit/activity_widgets.py:393
msgid "Activity"
diff --git a/po/hus.po b/po/hus.po
index 9077a20..b9b67ef 100644
--- a/po/hus.po
+++ b/po/hus.po
@@ -7,8 +7,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-07 00:36-0400\n"
-"PO-Revision-Date: 2012-03-01 08:57+0200\n"
-"Last-Translator: Chris <cjl@laptop.org>\n"
+"PO-Revision-Date: 2012-06-24 02:20+0200\n"
+"Last-Translator: ing.arturo.lara <ing.arturo.lara@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: hus\n"
"MIME-Version: 1.0\n"
@@ -68,7 +68,7 @@ msgstr "Alk'idh"
#: activity.py:292
msgid "Waveform"
-msgstr "ik' bel"
+msgstr "Ik' bel"
#: activity.py:293
msgid "Frequency"
@@ -113,7 +113,7 @@ msgstr "It k'wajat ti in alwa´yab ts'ot'k'odh, ts'ejlixna ani ka kániy jita'."
#: brain.py:33 voice.py:67
msgid "Spanish"
-msgstr "labkaw"
+msgstr "Labkaw"
#: brain.py:37 brain.py:52 brain.py:54 brain.py:64 voice.py:58
msgid "English"
diff --git a/po/hy.po b/po/hy.po
index 3b02e2c..0a6610d 100644
--- a/po/hy.po
+++ b/po/hy.po
@@ -27,7 +27,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-07 00:36-0400\n"
-"PO-Revision-Date: 2011-12-24 04:51+0200\n"
+"PO-Revision-Date: 2012-07-01 06:19+0200\n"
"Last-Translator: Chris <cjl@laptop.org>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: hy\n"
@@ -197,7 +197,7 @@ msgstr "Իմ հարևանությունը"
#: toolkit/activity_widgets.py:213
msgid "Description"
-msgstr ""
+msgstr "Նկարագրություն"
#: toolkit/activity_widgets.py:393
msgid "Activity"
diff --git a/po/pt.po b/po/pt.po
index 60754b0..e80c13e 100644
--- a/po/pt.po
+++ b/po/pt.po
@@ -35,8 +35,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-07 00:36-0400\n"
-"PO-Revision-Date: 2012-04-03 00:34+0200\n"
-"Last-Translator: Eduardo H. <hoboprimate@gmail.com>\n"
+"PO-Revision-Date: 2012-06-25 01:45+0200\n"
+"Last-Translator: Luis <valente.luis@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: pt\n"
"MIME-Version: 1.0\n"
@@ -202,7 +202,7 @@ msgstr "A Minha Vizinhança"
#: toolkit/activity_widgets.py:213
msgid "Description"
-msgstr ""
+msgstr "Descrição"
#: toolkit/activity_widgets.py:393
msgid "Activity"
diff --git a/po/quz.po b/po/quz.po
index 7ae7436..75349c3 100644
--- a/po/quz.po
+++ b/po/quz.po
@@ -4,7 +4,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-07 00:36-0400\n"
-"PO-Revision-Date: 2011-11-21 05:42+0200\n"
+"PO-Revision-Date: 2012-07-16 01:58+0200\n"
"Last-Translator: Chris <cjl@laptop.org>\n"
"Language-Team: Voluntarios quechuas Sugar Camp Lima\n"
"Language: quz\n"
@@ -204,11 +204,12 @@ msgstr "wakinkunallapaq"
# "Vecindario"
#: toolkit/activity_widgets.py:135
msgid "My Neighborhood"
-msgstr "Ayllu"
+msgstr "Aylluy"
#: toolkit/activity_widgets.py:213
+#, fuzzy
msgid "Description"
-msgstr ""
+msgstr "Riqsichinapaq"
# "Actividad"
#: toolkit/activity_widgets.py:393
@@ -363,7 +364,7 @@ msgstr "Eslovaco simi"
# "Polaco"
#: voice.py:68
msgid "Polish"
-msgstr "Poalco simi"
+msgstr "Polaco simi"
# "Esperanto"
#: voice.py:69
diff --git a/po/zh_CN.po b/po/zh_CN.po
index 37ed8af..83c3e4e 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -38,9 +38,9 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-06-30 00:32-0400\n"
-"PO-Revision-Date: 2012-07-03 13:10+0200\n"
-"Last-Translator: athurg <feng@jianbo.de>\n"
+"POT-Creation-Date: 2012-06-07 00:36-0400\n"
+"PO-Revision-Date: 2012-06-24 01:47+0200\n"
+"Last-Translator: lite <litekok@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
@@ -73,98 +73,98 @@ msgstr "语音"
msgid "Face"
msgstr "脸"
-#: activity.py:196
+#: activity.py:194
#, python-format
msgid "Hello %s. Please Type something."
msgstr "你好%s。请输入。"
-#: activity.py:262
+#: activity.py:260
msgid "Pitch:"
msgstr "音高:"
-#: activity.py:275
+#: activity.py:273
msgid "Rate:"
msgstr "速度:"
-#: activity.py:283
+#: activity.py:281
msgid "pitch adjusted"
msgstr "音高调整"
-#: activity.py:287
+#: activity.py:285
msgid "rate adjusted"
msgstr "速度调整"
-#: activity.py:293
+#: activity.py:291
msgid "Simple"
msgstr "简单"
-#: activity.py:295
+#: activity.py:292
msgid "Waveform"
msgstr "波形"
-#: activity.py:296
+#: activity.py:293
msgid "Frequency"
msgstr "频率"
-#: activity.py:301
+#: activity.py:298
msgid "Mouth:"
msgstr "口形:"
-#: activity.py:305
+#: activity.py:302
msgid "Round"
msgstr "圆"
-#: activity.py:306
+#: activity.py:303
msgid "Glasses"
msgstr "眼镜"
-#: activity.py:311
+#: activity.py:308
msgid "Eyes:"
msgstr "眼睛:"
-#: activity.py:322
+#: activity.py:319
msgid "Eyes number:"
msgstr "眼睛数量:"
-#: activity.py:334
+#: activity.py:331
msgid "mouth changed"
msgstr "口形已改变"
-#: activity.py:346
+#: activity.py:343
msgid "eyes changed"
msgstr "眼睛已改变"
-#: activity.py:446
+#: activity.py:442
#, python-format
msgid "Sorry, I can't speak %(old_voice)s, let's talk %(new_voice)s instead."
msgstr "对不起,我不能说%(old_voice)s,让我们来谈谈%(new_voice)s。"
-#: activity.py:477
+#: activity.py:473
msgid "You are in off-line mode, share and invite someone."
msgstr "您正在离线模式,请分享,并邀请其他人。"
-#: brain.py:34 voice.py:69
+#: brain.py:33 voice.py:67
msgid "Spanish"
msgstr "西班牙"
-#: brain.py:38 brain.py:53 brain.py:55 brain.py:65 voice.py:60
+#: brain.py:37 brain.py:52 brain.py:54 brain.py:64 voice.py:58
msgid "English"
msgstr "英语"
-#: brain.py:81
+#: brain.py:80
msgid "Sorry, I can't understand what you are asking about."
msgstr "对不起,我不明白你的问题。"
-#: brain.py:105
+#: brain.py:104
msgid ""
"Sorry, there is no free memory to load my brain. Close other activities and "
"try once more."
msgstr "很抱歉,没有空余的内存来载入我的大脑。请关闭其他的活动,并再试一次。"
-#: brain.py:126
+#: brain.py:125
#, python-format
-msgid "Hello, I'm a robot \"%s\". "
-msgstr "Hello,我是机器人“ %s ”"
+msgid "Hello, I'm a robot \"%s\". Please ask me any question."
+msgstr "你好,我是机器人“%s”。请向我提问题。"
#: chatbox.py:248
msgid "URL from Chat"
@@ -204,136 +204,132 @@ msgstr "我的邻居"
#: toolkit/activity_widgets.py:213
msgid "Description"
-msgstr "描述:"
+msgstr "描述"
#: toolkit/activity_widgets.py:393
msgid "Activity"
msgstr "活动"
-#: voice.py:39
+#: voice.py:37
msgid "Brazil"
msgstr "巴西"
-#: voice.py:40
+#: voice.py:38
msgid "Swedish"
msgstr "瑞典语"
-#: voice.py:41
+#: voice.py:39
msgid "Icelandic"
msgstr "冰岛语"
-#: voice.py:42
+#: voice.py:40
msgid "Romanian"
msgstr "罗马尼亚语"
-#: voice.py:43
+#: voice.py:41
msgid "Swahili"
msgstr "斯瓦希里语"
-#: voice.py:44
+#: voice.py:42
msgid "Hindi"
msgstr "印地语"
-#: voice.py:45
+#: voice.py:43
msgid "Dutch"
msgstr "荷兰语"
-#: voice.py:46
+#: voice.py:44
msgid "Latin"
msgstr "拉丁语"
-#: voice.py:47
+#: voice.py:45
msgid "Hungarian"
msgstr "匈牙利语"
-#: voice.py:48
+#: voice.py:46
msgid "Macedonian"
msgstr "马其顿语"
-#: voice.py:49
+#: voice.py:47
msgid "Welsh"
msgstr "威尔士语"
-#: voice.py:50
+#: voice.py:48
msgid "French"
msgstr "法语"
-#: voice.py:51
+#: voice.py:49
msgid "Norwegian"
msgstr "新挪威语"
-#: voice.py:52
+#: voice.py:50
msgid "Russian"
msgstr "俄语"
-#: voice.py:53
+#: voice.py:51
msgid "Afrikaans"
msgstr "南非荷兰语"
-#: voice.py:54
+#: voice.py:52
msgid "Finnish"
msgstr "芬兰语"
-#: voice.py:55 voice.py:158
+#: voice.py:53 voice.py:157
msgid "Default"
msgstr "默认"
-#: voice.py:56
+#: voice.py:54
msgid "Cantonese"
msgstr "粤语"
-#: voice.py:57
+#: voice.py:55
msgid "Scottish"
msgstr "苏格兰语"
-#: voice.py:58
+#: voice.py:56
msgid "Greek"
msgstr "现代希腊语"
-#: voice.py:59
+#: voice.py:57
msgid "Vietnam"
msgstr "越南语"
-#: voice.py:61
+#: voice.py:59
msgid "Lancashire"
msgstr "兰开夏郡"
-#: voice.py:62
+#: voice.py:60
msgid "Italian"
msgstr "意大利语"
-#: voice.py:63
+#: voice.py:61
msgid "Portugal"
msgstr "葡萄牙语"
-#: voice.py:64
+#: voice.py:62
msgid "German"
msgstr "德语"
-#: voice.py:65
+#: voice.py:63
msgid "Whisper"
msgstr "悄悄话"
-#: voice.py:66
+#: voice.py:64
msgid "Croatian"
msgstr "克罗地亚语"
-#: voice.py:67
+#: voice.py:65
msgid "Czech"
msgstr "捷克语"
-#: voice.py:68
+#: voice.py:66
msgid "Slovak"
msgstr "斯洛伐克语"
-#: voice.py:70
+#: voice.py:68
msgid "Polish"
msgstr "波兰语"
-#: voice.py:71
+#: voice.py:69
msgid "Esperanto"
msgstr "世界语"
-
-#, python-format
-#~ msgid "Hello, I'm a robot \"%s\". Please ask me any question."
-#~ msgstr "你好,我是机器人“%s”。请向我提问题。"
diff --git a/roundbox.py b/roundbox.py
deleted file mode 100644
index 35d5f7c..0000000
--- a/roundbox.py
+++ /dev/null
@@ -1,100 +0,0 @@
-import math
-import gtk
-from sugar.graphics import style
-
-
-class RoundBox(gtk.HBox):
- __gtype_name__ = 'RoundBox'
-
- _BORDER_DEFAULT = style.LINE_WIDTH
-
- def __init__(self, **kwargs):
- gtk.HBox.__init__(self, **kwargs)
-
- self._x = None
- self._y = None
- self._width = None
- self._height = None
- self._radius = style.zoom(10)
- self.border = self._BORDER_DEFAULT
- self.border_color = style.COLOR_BLACK
- self.background_color = None
- self.set_reallocate_redraws(True)
- self.set_resize_mode(gtk.RESIZE_PARENT)
- self.connect("expose_event", self.__expose_cb)
- self.connect("add", self.__add_cb)
-
- def __add_cb(self, child, params):
- child.set_border_width(style.zoom(5))
-
- def __size_allocate_cb(self, widget, allocation):
- self._x = allocation.x
- self._y = allocation.y
- self._width = allocation.width
- self._height = allocation.height
-
- def __expose_cb(self, widget, event):
- context = widget.window.cairo_create()
-
- # set a clip region for the expose event
- context.rectangle(event.area.x, event.area.y,
- event.area.width, event.area.height)
- context.clip()
- self.draw(context)
- return False
-
- def draw(self, cr):
- rect = self.get_allocation()
- x = rect.x + self._BORDER_DEFAULT / 2
- y = rect.y + self._BORDER_DEFAULT / 2
- width = rect.width - self._BORDER_DEFAULT
- height = rect.height - self._BORDER_DEFAULT
-
- cr.move_to(x + self._radius, y)
- cr.arc(x + width - self._radius, y + self._radius,
- self._radius, math.pi * 1.5, math.pi * 2)
- cr.arc(x + width - self._radius, y + height - self._radius,
- self._radius, 0, math.pi * 0.5)
- cr.arc(x + self._radius, y + height - self._radius,
- self._radius, math.pi * 0.5, math.pi)
- cr.arc(x + self._radius, y + self._radius, self._radius,
- math.pi, math.pi * 1.5)
- cr.close_path()
-
- if self.background_color is not None:
- r, g, b, __ = self.background_color.get_rgba()
- cr.set_source_rgb(r, g, b)
- cr.fill_preserve()
-
- if self.border_color is not None:
- r, g, b, __ = self.border_color.get_rgba()
- cr.set_source_rgb(r, g, b)
- cr.set_line_width(self.border)
- cr.stroke()
-
-if __name__ == '__main__':
-
- win = gtk.Window()
- win.connect('destroy', gtk.main_quit)
- win.set_default_size(450, 550)
- vbox = gtk.VBox()
-
- box1 = RoundBox()
- vbox.add(box1)
- label1 = gtk.Label("Test 1")
- box1.add(label1)
-
- rbox = RoundBox()
- rbox.background_color = style.Color('#FF0000')
- vbox.add(rbox)
- label2 = gtk.Label("Test 2")
- rbox.add(label2)
-
- bbox = RoundBox()
- bbox.background_color = style.Color('#aaff33')
- bbox.border_color = style.Color('#ff3300')
- vbox.add(bbox)
-
- win.add(vbox)
- win.show_all()
- gtk.main()
diff --git a/setup.py b/setup.py
index 745c6ec..7d23ceb 100755
--- a/setup.py
+++ b/setup.py
@@ -9,17 +9,17 @@
#
# Parts of Speak.activity are based on code from Measure.activity
# Copyright (C) 2007 Arjun Sarwal - arjun@laptop.org
-#
+#
# Speak.activity 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 3 of the License, or
# (at your option) any later version.
-#
+#
# Speak.activity 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 Speak.activity. If not, see <http://www.gnu.org/licenses/>.
diff --git a/shared_activity.py b/shared_activity.py
deleted file mode 100644
index a537958..0000000
--- a/shared_activity.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# Copyright (C) 2009, Aleksey Lim
-#
-# 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
-
-"""Extend sugar-toolkit activity class"""
-
-import logging
-import telepathy
-
-from sugar.activity import activity
-from sugar.presence.sugartubeconn import SugarTubeConnection
-
-
-class SharedActivity(activity.Activity):
- """Basic activity class with sharing features"""
-
- def __init__(self, canvas, service, handle):
- """
- Initialise the Activity.
-
- canvas -- gtk.Widget
- root widget for activity content
-
- service -- string
- dbus service for activity
-
- handle -- sugar.activity.activityhandle.ActivityHandle
- instance providing the activity id and access to the
- presence service which *may* provide sharing for this
- application
-
- """
- activity.Activity.__init__(self, handle)
- self.set_canvas(canvas)
- self.service = service
-
- 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 _shared_cb(self, activity):
- logging.debug('My activity was shared')
- self.__initiator = True
- self._sharing_setup()
-
- logging.debug('This is my activity: making a tube...')
- self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
- self.service, {})
-
- def _joined_cb(self, activity):
- if not self._shared_activity:
- return
-
- logging.debug('Joined an existing shared activity')
-
- self.__initiator = False
- self._sharing_setup()
-
- logging.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:
- logging.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):
- logging.error('ListTubes() failed: %s', e)
-
- def _new_tube_cb(self, id, initiator, type, service, params, state):
- logging.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])
-
- self._share(tube_conn, self.__initiator)
diff --git a/toolkit/__init__.py b/toolkit/__init__.py
new file mode 100644
index 0000000..17a92ac
--- /dev/null
+++ b/toolkit/__init__.py
@@ -0,0 +1,16 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
diff --git a/toolkit/activity.py b/toolkit/activity.py
new file mode 100644
index 0000000..1512610
--- /dev/null
+++ b/toolkit/activity.py
@@ -0,0 +1,331 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# 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
+
+"""Extend sugar-toolkit activity class"""
+
+import gtk
+import logging
+import telepathy
+import gobject
+
+from sugar.activity import activity
+from sugar.presence.sugartubeconn import SugarTubeConnection
+from sugar.graphics.alert import ConfirmationAlert, NotifyAlert
+
+
+_NEW_INSTANCE = 0
+_NEW_INSTANCE = 1
+_PRE_INSTANCE = 2
+_POST_INSTANCE = 3
+
+
+class CursorFactory:
+
+ __shared_state = {"cursors": {}}
+
+ def __init__(self):
+ self.__dict__ = self.__shared_state
+
+ def get_cursor(self, cur_type):
+ if not self.cursors.has_key(cur_type):
+ cur = gtk.gdk.Cursor(cur_type)
+ self.cursors[cur_type] = cur
+ return self.cursors[cur_type]
+
+
+class Activity(activity.Activity):
+
+ """Basic activity class"""
+
+ def new_instance(self):
+ """
+ New instance was created.
+
+ Will be invoked after __init__() instead of resume_instance().
+ Subclass should implement this method to catch creation stage.
+ """
+ pass
+
+ def resume_instance(self, filepath):
+ """
+ Instance was resumed.
+
+ Will be invoked after __init__() instead of new_instance().
+ Subclass should implement this method to catch resuming stage.
+
+ """
+ pass
+
+ def save_instance(self, filepath):
+ """
+ Save activity instance.
+
+ Subclass should implement this method to save activity data.
+ """
+ raise NotImplementedError
+
+ def on_save_instance(self, cb, *args):
+ """ Register callback which will be invoked before save_instance """
+ self.__on_save_instance.append((cb, args))
+
+ def share_instance(self, connection, is_initiator):
+ """
+ Activity was shared/joined.
+
+ connection -- SugarTubeConnection object
+ wich represents telepathy connection
+
+ is_initiator -- boolean
+ if True activity was shared and
+ (current activity is an initiator of sharing)
+ otherwise activity was joined(to existed sharing session)
+
+ Will be invoked after __init__() and {new,resume}_instance().
+ Subclass should implement this method to catch sharing stage.
+ """
+ pass
+
+ def set_toolbar_box(self, toolbox):
+ if hasattr(activity.Activity, 'set_toolbar_box'):
+ activity.Activity.set_toolbar_box(self, toolbox)
+ else:
+ self.set_toolbox(toolbox)
+
+ def get_toolbar_box(self):
+ if hasattr(activity.Activity, 'get_toolbar_box'):
+ return activity.Activity.get_toolbar_box(self)
+ else:
+ return self.get_toolbox()
+
+ toolbar_box = property(get_toolbar_box, set_toolbar_box)
+
+ def get_shared_activity(self):
+ if hasattr(activity.Activity, 'get_shared_activity'):
+ return activity.Activity.get_shared_activity(self)
+ else:
+ return self._shared_activity
+
+ def notify_alert(self, title, msg):
+ """Raise standard notify alert"""
+ alert = NotifyAlert(title=title, msg=msg)
+
+ def response(alert, response_id, self):
+ self.remove_alert(alert)
+
+ alert.connect('response', response, self)
+ alert.show_all()
+ self.add_alert(alert)
+
+ def confirmation_alert(self, title, msg, cb, *cb_args):
+ """Raise standard confirmation alert"""
+ alert = ConfirmationAlert(title=title, msg=msg)
+
+ def response(alert, response_id, self, cb, *cb_args):
+ self.remove_alert(alert)
+ if response_id is gtk.RESPONSE_OK:
+ cb(*cb_args)
+
+ alert.connect('response', response, self, cb, *cb_args)
+ alert.show_all()
+ self.add_alert(alert)
+
+ def get_cursor(self):
+ return self._cursor
+
+ def set_cursor(self, cursor):
+ if not isinstance(cursor, gtk.gdk.Cursor):
+ cursor = CursorFactory().get_cursor(cursor)
+
+ if self._cursor != cursor:
+ self._cursor = cursor
+ self.window.set_cursor(self._cursor)
+
+ def __init__(self, canvas, handle):
+ """
+ Initialise the Activity.
+
+ canvas -- gtk.Widget
+ root widget for activity content
+
+ handle -- sugar.activity.activityhandle.ActivityHandle
+ instance providing the activity id and access to the
+
+ """
+ activity.Activity.__init__(self, handle)
+
+ if handle.object_id:
+ self.__state = _NEW_INSTANCE
+ else:
+ self.__state = _NEW_INSTANCE
+
+ self.__resume_filename = None
+ self.__postponed_share = []
+ self.__on_save_instance = []
+
+ self._cursor = None
+ self.set_cursor(gtk.gdk.LEFT_PTR)
+
+ # 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_canvasactivity_cb)
+ self.set_canvas(canvas)
+
+ def __instance(self):
+ logging.debug('Activity.__instance')
+
+ if self.__resume_filename:
+ self.resume_instance(self.__resume_filename)
+ else:
+ self.new_instance()
+
+ for i in self.__postponed_share:
+ self.share_instance(*i)
+ self.__postponed_share = []
+
+ self.__state = _POST_INSTANCE
+
+ def read_file(self, filepath):
+ """Subclass should not override this method"""
+ logging.debug('Activity.read_file state=%s' % self.__state)
+
+ self.__resume_filename = filepath
+
+ if self.__state == _NEW_INSTANCE:
+ self.__state = _PRE_INSTANCE
+ elif self.__state == _PRE_INSTANCE:
+ self.__instance();
+
+ def write_file(self, filepath):
+ """Subclass should not override this method"""
+ for cb, args in self.__on_save_instance:
+ cb(*args)
+ self.save_instance(filepath)
+
+ def __map_canvasactivity_cb(self, widget):
+ logging.debug('Activity.__map_canvasactivity_cb state=%s' % \
+ self.__state)
+
+ if self.__state == _NEW_INSTANCE:
+ self.__instance()
+ elif self.__state == _NEW_INSTANCE:
+ self.__state = _PRE_INSTANCE
+ elif self.__state == _PRE_INSTANCE:
+ self.__instance();
+
+ return False
+
+ def _share(self, tube_conn, initiator):
+ logging.debug('Activity._share state=%s' % self.__state)
+
+ if self.__state == _NEW_INSTANCE:
+ self.__postponed_share.append((tube_conn, initiator))
+ self.__state = _PRE_INSTANCE
+ elif self.__state == _PRE_INSTANCE:
+ self.__postponed_share.append((tube_conn, initiator))
+ self.__instance();
+ elif self.__state == _POST_INSTANCE:
+ self.share_instance(tube_conn, initiator)
+
+
+class SharedActivity(Activity):
+ """Basic activity class with sharing features"""
+
+ def __init__(self, canvas, service, handle):
+ """
+ Initialise the Activity.
+
+ canvas -- gtk.Widget
+ root widget for activity content
+
+ service -- string
+ dbus service for activity
+
+ handle -- sugar.activity.activityhandle.ActivityHandle
+ instance providing the activity id and access to the
+ presence service which *may* provide sharing for this
+ application
+
+ """
+ Activity.__init__(self, canvas, handle)
+ self.service = service
+
+ 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 _shared_cb(self, activity):
+ logging.debug('My activity was shared')
+ self.__initiator = True
+ self._sharing_setup()
+
+ logging.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
+
+ logging.debug('Joined an existing shared activity')
+
+ self.__initiator = False
+ self._sharing_setup()
+
+ logging.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:
+ logging.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):
+ logging.error('ListTubes() failed: %s', e)
+
+ def _new_tube_cb(self, id, initiator, type, service, params, state):
+ logging.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])
+
+ self._share(tube_conn, self.__initiator)
diff --git a/toolkit/activity_widgets.py b/toolkit/activity_widgets.py
new file mode 100644
index 0000000..9195af5
--- /dev/null
+++ b/toolkit/activity_widgets.py
@@ -0,0 +1,397 @@
+# Copyright (C) 2009, Aleksey Lim, Simon Schampijer
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gtk
+import gobject
+import gettext
+
+from sugar import profile
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.radiotoolbutton import RadioToolButton
+from sugar.graphics.toolbox import Toolbox
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics.icon import Icon
+from sugar.bundle.activitybundle import ActivityBundle
+
+from toolkit.toolbarbox import ToolbarButton
+from toolkit.radiopalette import RadioPalette
+from toolkit.radiopalette import RadioMenuButton
+from sugar.graphics import style
+
+_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
+
+
+def _create_activity_icon(metadata):
+ if metadata.get('icon-color', ''):
+ color = XoColor(metadata['icon-color'])
+ else:
+ color = profile.get_color()
+
+ from sugar.activity.activity import get_bundle_path
+ bundle = ActivityBundle(get_bundle_path())
+ icon = Icon(file=bundle.get_icon(), xo_color=color)
+
+ return icon
+
+
+class ActivityButton(ToolButton):
+
+ def __init__(self, activity, **kwargs):
+ ToolButton.__init__(self, **kwargs)
+
+ icon = _create_activity_icon(activity.metadata)
+ self.set_icon_widget(icon)
+ icon.show()
+
+ self.props.tooltip = activity.metadata['title']
+ activity.metadata.connect('updated', self.__jobject_updated_cb)
+
+ def __jobject_updated_cb(self, jobject):
+ self.props.tooltip = jobject['title']
+
+
+class ActivityToolbarButton(ToolbarButton):
+
+ def __init__(self, activity, **kwargs):
+ toolbar = ActivityToolbar(activity, orientation_left=True)
+ toolbar.stop.hide()
+
+ ToolbarButton.__init__(self, page=toolbar, **kwargs)
+
+ icon = _create_activity_icon(activity.metadata)
+ self.set_icon_widget(icon)
+ icon.show()
+
+
+class StopButton(ToolButton):
+
+ def __init__(self, activity, **kwargs):
+ ToolButton.__init__(self, 'activity-stop', **kwargs)
+ self.props.tooltip = _('Stop')
+ self.props.accelerator = '<Ctrl>Q'
+ self.connect('clicked', self.__stop_button_clicked_cb, activity)
+
+ def __stop_button_clicked_cb(self, button, activity):
+ activity.close()
+
+
+class UndoButton(ToolButton):
+
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, 'edit-undo', **kwargs)
+ self.props.tooltip = _('Undo')
+ self.props.accelerator = '<Ctrl>Z'
+
+
+class RedoButton(ToolButton):
+
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, 'edit-redo', **kwargs)
+ self.props.tooltip = _('Redo')
+
+
+class CopyButton(ToolButton):
+
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, 'edit-copy', **kwargs)
+ self.props.tooltip = _('Copy')
+
+
+class PasteButton(ToolButton):
+
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, 'edit-paste', **kwargs)
+ self.props.tooltip = _('Paste')
+
+
+class ShareButton(RadioMenuButton):
+
+ def __init__(self, activity, **kwargs):
+ palette = RadioPalette()
+
+ self.private = RadioToolButton(
+ icon_name='zoom-home')
+ palette.append(self.private, _('Private'))
+
+ self.neighborhood = RadioToolButton(
+ icon_name='zoom-neighborhood',
+ group=self.private)
+ self._neighborhood_handle = self.neighborhood.connect(
+ 'clicked', self.__neighborhood_clicked_cb, activity)
+ palette.append(self.neighborhood, _('My Neighborhood'))
+
+ activity.connect('shared', self.__update_share_cb)
+ activity.connect('joined', self.__update_share_cb)
+
+ RadioMenuButton.__init__(self, **kwargs)
+ self.props.palette = palette
+ if activity.props.max_participants == 1:
+ self.props.sensitive = False
+
+ def __neighborhood_clicked_cb(self, button, activity):
+ activity.share()
+
+ def __update_share_cb(self, activity):
+ self.neighborhood.handler_block(self._neighborhood_handle)
+ try:
+ if activity.get_shared():
+ self.private.props.sensitive = False
+ self.neighborhood.props.sensitive = False
+ self.neighborhood.props.active = True
+ else:
+ self.private.props.sensitive = True
+ self.neighborhood.props.sensitive = True
+ self.private.props.active = True
+ finally:
+ self.neighborhood.handler_unblock(self._neighborhood_handle)
+
+
+class TitleEntry(gtk.ToolItem):
+
+ def __init__(self, activity, **kwargs):
+ gtk.ToolItem.__init__(self)
+ self.set_expand(False)
+ self._update_title_sid = None
+
+ self.entry = gtk.Entry(**kwargs)
+ self.entry.set_size_request(int(gtk.gdk.screen_width() / 3), -1)
+ self.entry.set_text(activity.metadata['title'])
+ self.entry.connect('changed', self.__title_changed_cb, activity)
+ self.entry.show()
+ self.add(self.entry)
+
+ activity.metadata.connect('updated', self.__jobject_updated_cb)
+
+ def modify_bg(self, state, color):
+ gtk.ToolItem.modify_bg(self, state, color)
+ self.entry.modify_bg(state, color)
+
+ def __jobject_updated_cb(self, jobject):
+ self.entry.set_text(jobject['title'])
+
+ def __title_changed_cb(self, entry, activity):
+ if not self._update_title_sid:
+ self._update_title_sid = gobject.timeout_add_seconds(
+ 1, self.__update_title_cb, activity)
+
+ def __update_title_cb(self, activity):
+ title = self.entry.get_text()
+
+ activity.metadata['title'] = title
+ activity.metadata['title_set_by_user'] = '1'
+ activity.save()
+
+ shared_activity = activity.get_shared_activity()
+ if shared_activity is not None:
+ shared_activity.props.name = title
+
+ self._update_title_sid = None
+ return False
+
+
+class DescriptionItem(gtk.ToolItem):
+
+ def __init__(self, activity, **kwargs):
+ gtk.ToolItem.__init__(self)
+
+ description_button = ToolButton('edit-description')
+ description_button.show()
+ description_button.set_tooltip(_('Description'))
+ self._palette = description_button.get_palette()
+
+ description_box = gtk.HBox()
+ sw = gtk.ScrolledWindow()
+ sw.set_size_request(int(gtk.gdk.screen_width() / 2),
+ 2 * style.GRID_CELL_SIZE)
+ sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self._text_view = gtk.TextView()
+ self._text_view.set_left_margin(style.DEFAULT_PADDING)
+ self._text_view.set_right_margin(style.DEFAULT_PADDING)
+ text_buffer = gtk.TextBuffer()
+ if 'description' in activity.metadata:
+ text_buffer.set_text(activity.metadata['description'])
+ self._text_view.set_buffer(text_buffer)
+ self._text_view.connect('focus-out-event',
+ self.__description_changed_cb, activity)
+ sw.add(self._text_view)
+ description_box.pack_start(sw, False, True, 0)
+ self._palette.set_content(description_box)
+ description_box.show_all()
+
+ self.add(description_button)
+ description_button.connect('clicked',
+ self.__description_button_clicked_cb)
+
+ activity.metadata.connect('updated', self.__jobject_updated_cb)
+
+ def _get_text_from_buffer(self):
+ buf = self._text_view.get_buffer()
+ start_iter = buf.get_start_iter()
+ end_iter = buf.get_end_iter()
+ return buf.get_text(start_iter, end_iter, False)
+
+ def __jobject_updated_cb(self, jobject):
+ if self._text_view.has_focus():
+ return
+ if 'description' not in jobject:
+ return
+ if self._get_text_from_buffer() == jobject['description']:
+ return
+ buf = self._text_view.get_buffer()
+ buf.set_text(jobject['description'])
+
+ def __description_button_clicked_cb(self, button):
+ self._palette.popup(immediate=True, state=1)
+
+ def __description_changed_cb(self, widget, event, activity):
+ description = self._get_text_from_buffer()
+ if 'description' in activity.metadata and \
+ description == activity.metadata['description']:
+ return
+
+ activity.metadata['description'] = description
+ activity.save()
+ return False
+
+
+class ActivityToolbar(gtk.Toolbar):
+ """The Activity toolbar with the Journal entry title, sharing,
+ and Stop buttons
+
+ All activities should have this toolbar. It is easiest to add it to your
+ Activity by using the ActivityToolbox.
+ """
+
+ def __init__(self, activity, orientation_left=False):
+ gtk.Toolbar.__init__(self)
+
+ self._activity = activity
+
+ if activity.metadata:
+ title_button = TitleEntry(activity)
+ title_button.show()
+ self.insert(title_button, -1)
+ self.title = title_button.entry
+
+ if orientation_left == False:
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = False
+ separator.set_expand(True)
+ self.insert(separator, -1)
+ separator.show()
+
+ if activity.metadata:
+ description_item = DescriptionItem(activity)
+ description_item.show()
+ self.insert(description_item, -1)
+
+ self.share = ShareButton(activity)
+ self.share.show()
+ self.insert(self.share, -1)
+
+ self.stop = StopButton(activity)
+ self.insert(self.stop, -1)
+ self.stop.show()
+
+
+class EditToolbar(gtk.Toolbar):
+ """Provides the standard edit toolbar for Activities.
+
+ Members:
+ undo -- the undo button
+ redo -- the redo button
+ copy -- the copy button
+ paste -- the paste button
+ separator -- A separator between undo/redo and copy/paste
+
+ This class only provides the 'edit' buttons in a standard layout,
+ your activity will need to either hide buttons which make no sense for your
+ Activity, or you need to connect the button events to your own callbacks:
+
+ ## Example from Read.activity:
+ # Create the edit toolbar:
+ self._edit_toolbar = EditToolbar(self._view)
+ # Hide undo and redo, they're not needed
+ self._edit_toolbar.undo.props.visible = False
+ self._edit_toolbar.redo.props.visible = False
+ # Hide the separator too:
+ self._edit_toolbar.separator.props.visible = False
+
+ # As long as nothing is selected, copy needs to be insensitive:
+ self._edit_toolbar.copy.set_sensitive(False)
+ # When the user clicks the button, call _edit_toolbar_copy_cb()
+ self._edit_toolbar.copy.connect('clicked', self._edit_toolbar_copy_cb)
+
+ # Add the edit toolbar:
+ toolbox.add_toolbar(_('Edit'), self._edit_toolbar)
+ # And make it visible:
+ self._edit_toolbar.show()
+ """
+
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ self.undo = UndoButton()
+ self.insert(self.undo, -1)
+ self.undo.show()
+
+ self.redo = RedoButton()
+ self.insert(self.redo, -1)
+ self.redo.show()
+
+ self.separator = gtk.SeparatorToolItem()
+ self.separator.set_draw(True)
+ self.insert(self.separator, -1)
+ self.separator.show()
+
+ self.copy = CopyButton()
+ self.insert(self.copy, -1)
+ self.copy.show()
+
+ self.paste = PasteButton()
+ self.insert(self.paste, -1)
+ self.paste.show()
+
+
+class ActivityToolbox(Toolbox):
+ """Creates the Toolbox for the Activity
+
+ By default, the toolbox contains only the ActivityToolbar. After creating
+ the toolbox, you can add your activity specific toolbars, for example the
+ EditToolbar.
+
+ To add the ActivityToolbox to your Activity in MyActivity.__init__() do:
+
+ # Create the Toolbar with the ActivityToolbar:
+ toolbox = activity.ActivityToolbox(self)
+ ... your code, inserting all other toolbars you need, like EditToolbar
+
+ # Add the toolbox to the activity frame:
+ self.set_toolbox(toolbox)
+ # And make it visible:
+ toolbox.show()
+ """
+
+ def __init__(self, activity):
+ Toolbox.__init__(self)
+
+ self._activity_toolbar = ActivityToolbar(activity)
+ self.add_toolbar(_('Activity'), self._activity_toolbar)
+ self._activity_toolbar.show()
+
+ def get_activity_toolbar(self):
+ return self._activity_toolbar
diff --git a/toolkit/chooser.py b/toolkit/chooser.py
new file mode 100644
index 0000000..e957fd7
--- /dev/null
+++ b/toolkit/chooser.py
@@ -0,0 +1,69 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# 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
+
+"""Object chooser method"""
+
+import gtk
+import logging
+
+from sugar import mime
+from sugar.graphics.objectchooser import ObjectChooser
+
+TEXT = hasattr(mime, 'GENERIC_TYPE_TEXT') and mime.GENERIC_TYPE_TEXT or None
+IMAGE = hasattr(mime, 'GENERIC_TYPE_IMAGE') and mime.GENERIC_TYPE_IMAGE or None
+AUDIO = hasattr(mime, 'GENERIC_TYPE_AUDIO') and mime.GENERIC_TYPE_AUDIO or None
+VIDEO = hasattr(mime, 'GENERIC_TYPE_VIDEO') and mime.GENERIC_TYPE_VIDEO or None
+LINK = hasattr(mime, 'GENERIC_TYPE_LINK') and mime.GENERIC_TYPE_LINK or None
+
+
+def pick(cb=None, default=None, parent=None, what=None):
+ """
+ Opens object chooser.
+
+ Method returns:
+
+ * cb(jobject), if object was choosen and cb is not None
+ * jobject, if object was choosen and cb is None
+ * default, otherwise
+
+ NOTE: 'what' makes sense only for sugar >= 0.84
+ """
+ what = what and {'what_filter': what} or {}
+ chooser = ObjectChooser(parent=parent, **what)
+
+ jobject = None
+ out = None
+
+ try:
+ if chooser.run() == gtk.RESPONSE_ACCEPT:
+ jobject = chooser.get_selected_object()
+ logging.debug('ObjectChooser: %r' % jobject)
+
+ if jobject and jobject.file_path:
+ if cb:
+ out = cb(jobject)
+ else:
+ out = jobject
+ finally:
+ if jobject and id(jobject) != id(out):
+ jobject.destroy()
+ chooser.destroy()
+ del chooser
+
+ if out:
+ return out
+ else:
+ return default
diff --git a/combobox.py b/toolkit/combobox.py
index d021106..d021106 100644
--- a/combobox.py
+++ b/toolkit/combobox.py
diff --git a/toolkit/internals/__init__.py b/toolkit/internals/__init__.py
new file mode 100644
index 0000000..17a92ac
--- /dev/null
+++ b/toolkit/internals/__init__.py
@@ -0,0 +1,16 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
diff --git a/toolkit/internals/palettewindow.py b/toolkit/internals/palettewindow.py
new file mode 100644
index 0000000..d8c4326
--- /dev/null
+++ b/toolkit/internals/palettewindow.py
@@ -0,0 +1,976 @@
+# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
+# Copyright (C) 2008, One Laptop Per Child
+# Copyright (C) 2009, Tomeu Vizoso
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+"""
+STABLE.
+"""
+
+import logging
+
+import gtk
+import gobject
+import hippo
+
+from sugar.graphics import palettegroup
+from sugar.graphics import animator
+from sugar.graphics import style
+
+
+def _calculate_gap(a, b):
+ """Helper function to find the gap position and size of widget a"""
+ # Test for each side if the palette and invoker are
+ # adjacent to each other.
+ gap = True
+
+ if a.y + a.height == b.y:
+ gap_side = gtk.POS_BOTTOM
+ elif a.x + a.width == b.x:
+ gap_side = gtk.POS_RIGHT
+ elif a.x == b.x + b.width:
+ gap_side = gtk.POS_LEFT
+ elif a.y == b.y + b.height:
+ gap_side = gtk.POS_TOP
+ else:
+ gap = False
+
+ if gap:
+ if gap_side == gtk.POS_BOTTOM or gap_side == gtk.POS_TOP:
+ gap_start = min(a.width, max(0, b.x - a.x))
+ gap_size = max(0, min(a.width,
+ (b.x + b.width) - a.x) - gap_start)
+ elif gap_side == gtk.POS_RIGHT or gap_side == gtk.POS_LEFT:
+ gap_start = min(a.height, max(0, b.y - a.y))
+ gap_size = max(0, min(a.height,
+ (b.y + b.height) - a.y) - gap_start)
+
+ if gap and gap_size > 0:
+ return (gap_side, gap_start, gap_size)
+ else:
+ return False
+
+
+class MouseSpeedDetector(gobject.GObject):
+
+ __gsignals__ = {
+ 'motion-slow': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'motion-fast': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+ _MOTION_SLOW = 1
+ _MOTION_FAST = 2
+
+ def __init__(self, parent, delay, thresh):
+ """Create MouseSpeedDetector object,
+ delay in msec
+ threshold in pixels (per tick of 'delay' msec)"""
+
+ gobject.GObject.__init__(self)
+
+ self._threshold = thresh
+ self._parent = parent
+ self._delay = delay
+ self._state = None
+ self._timeout_hid = None
+ self._mouse_pos = None
+
+ def start(self):
+ self.stop()
+
+ self._mouse_pos = self._get_mouse_position()
+ self._timeout_hid = gobject.timeout_add(self._delay, self._timer_cb)
+
+ def stop(self):
+ if self._timeout_hid is not None:
+ gobject.source_remove(self._timeout_hid)
+ self._state = None
+
+ def _get_mouse_position(self):
+ display = gtk.gdk.display_get_default()
+ screen_, x, y, mask_ = display.get_pointer()
+ return (x, y)
+
+ def _detect_motion(self):
+ oldx, oldy = self._mouse_pos
+ (x, y) = self._get_mouse_position()
+ self._mouse_pos = (x, y)
+
+ dist2 = (oldx - x)**2 + (oldy - y)**2
+ if dist2 > self._threshold**2:
+ return True
+ else:
+ return False
+
+ def _timer_cb(self):
+ motion = self._detect_motion()
+ if motion and self._state != self._MOTION_FAST:
+ self.emit('motion-fast')
+ self._state = self._MOTION_FAST
+ elif not motion and self._state != self._MOTION_SLOW:
+ self.emit('motion-slow')
+ self._state = self._MOTION_SLOW
+
+ return True
+
+
+class PaletteWindow(gtk.Window):
+
+ __gsignals__ = {
+ 'popup': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'popdown': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'activate': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self, **kwargs):
+ self._group_id = None
+ self._invoker = None
+ self._invoker_hids = []
+ self._cursor_x = 0
+ self._cursor_y = 0
+ self._alignment = None
+ self._up = False
+ self._old_alloc = None
+
+ self._popup_anim = animator.Animator(.5, 10)
+ self._popup_anim.add(_PopupAnimation(self))
+
+ self._popdown_anim = animator.Animator(0.6, 10)
+ self._popdown_anim.add(_PopdownAnimation(self))
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ self.set_decorated(False)
+ self.set_resizable(False)
+ # Just assume xthickness and ythickness are the same
+ self.set_border_width(self.get_style().xthickness)
+
+ accel_group = gtk.AccelGroup()
+ self.set_data('sugar-accel-group', accel_group)
+ self.add_accel_group(accel_group)
+
+ self.set_group_id("default")
+
+ self.connect('show', self.__show_cb)
+ self.connect('hide', self.__hide_cb)
+ self.connect('realize', self.__realize_cb)
+ self.connect('destroy', self.__destroy_cb)
+ self.connect('enter-notify-event', self.__enter_notify_event_cb)
+ self.connect('leave-notify-event', self.__leave_notify_event_cb)
+
+ self._mouse_detector = MouseSpeedDetector(self, 200, 5)
+ self._mouse_detector.connect('motion-slow', self._mouse_slow_cb)
+
+ def __destroy_cb(self, palette):
+ self.set_group_id(None)
+
+ def set_invoker(self, invoker):
+ for hid in self._invoker_hids[:]:
+ self._invoker.disconnect(hid)
+ self._invoker_hids.remove(hid)
+
+ self._invoker = invoker
+ if invoker is not None:
+ self._invoker_hids.append(self._invoker.connect(
+ 'mouse-enter', self._invoker_mouse_enter_cb))
+ self._invoker_hids.append(self._invoker.connect(
+ 'mouse-leave', self._invoker_mouse_leave_cb))
+ self._invoker_hids.append(self._invoker.connect(
+ 'right-click', self._invoker_right_click_cb))
+
+ def get_invoker(self):
+ return self._invoker
+
+ invoker = gobject.property(type=object,
+ getter=get_invoker,
+ setter=set_invoker)
+
+ def __realize_cb(self, widget):
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+
+ def _mouse_slow_cb(self, widget):
+ self._mouse_detector.stop()
+ self._palette_do_popup()
+
+ def _palette_do_popup(self):
+ immediate = False
+
+ if self.is_up():
+ self._popdown_anim.stop()
+ return
+
+ if self._group_id:
+ group = palettegroup.get_group(self._group_id)
+ if group and group.is_up():
+ immediate = True
+ group.popdown()
+
+ self.popup(immediate=immediate)
+
+ def is_up(self):
+ return self._up
+
+ def set_group_id(self, group_id):
+ if self._group_id:
+ group = palettegroup.get_group(self._group_id)
+ group.remove(self)
+ if group_id:
+ self._group_id = group_id
+ group = palettegroup.get_group(group_id)
+ group.add(self)
+
+ def get_group_id(self):
+ return self._group_id
+
+ group_id = gobject.property(type=str,
+ getter=get_group_id,
+ setter=set_group_id)
+
+ def do_size_request(self, requisition):
+ gtk.Window.do_size_request(self, requisition)
+ requisition.width = max(requisition.width, style.GRID_CELL_SIZE * 2)
+
+ def do_size_allocate(self, allocation):
+ gtk.Window.do_size_allocate(self, allocation)
+
+ if self._old_alloc is None or \
+ self._old_alloc.x != allocation.x or \
+ self._old_alloc.y != allocation.y or \
+ self._old_alloc.width != allocation.width or \
+ self._old_alloc.height != allocation.height:
+ self.queue_draw()
+
+ # We need to store old allocation because when size_allocate
+ # is called widget.allocation is already updated.
+ # gtk.Window resizing is different from normal containers:
+ # the X window is resized, widget.allocation is updated from
+ # the configure request handler and finally size_allocate is called.
+ self._old_alloc = allocation
+
+ def do_expose_event(self, event):
+ # We want to draw a border with a beautiful gap
+ if self._invoker is not None and self._invoker.has_rectangle_gap():
+ invoker = self._invoker.get_rect()
+ palette = self.get_rect()
+
+ gap = _calculate_gap(palette, invoker)
+ else:
+ gap = False
+
+ allocation = self.get_allocation()
+ wstyle = self.get_style()
+
+ if gap:
+ wstyle.paint_box_gap(event.window, gtk.STATE_PRELIGHT,
+ gtk.SHADOW_IN, event.area, self, "palette",
+ 0, 0, allocation.width, allocation.height,
+ gap[0], gap[1], gap[2])
+ else:
+ wstyle.paint_box(event.window, gtk.STATE_PRELIGHT,
+ gtk.SHADOW_IN, event.area, self, "palette",
+ 0, 0, allocation.width, allocation.height)
+
+ # Fall trough to the container expose handler.
+ # (Leaving out the window expose handler which redraws everything)
+ gtk.Bin.do_expose_event(self, event)
+
+ def update_position(self):
+ invoker = self._invoker
+ if invoker is None or self._alignment is None:
+ logging.error('Cannot update the palette position.')
+ return
+
+ rect = self.size_request()
+ position = invoker.get_position_for_alignment(self._alignment, rect)
+ if position is None:
+ position = invoker.get_position(rect)
+
+ self.move(position.x, position.y)
+
+ def get_full_size_request(self):
+ return self.size_request()
+
+ def popup(self, immediate=False):
+ if self._invoker is not None:
+ full_size_request = self.get_full_size_request()
+ self._alignment = self._invoker.get_alignment(full_size_request)
+
+ self.update_position()
+ self.set_transient_for(self._invoker.get_toplevel())
+
+ self._popdown_anim.stop()
+
+ if not immediate:
+ self._popup_anim.start()
+ else:
+ self._popup_anim.stop()
+ self.show()
+ # we have to invoke update_position() twice
+ # since WM could ignore first move() request
+ self.update_position()
+
+ def popdown(self, immediate=False):
+ logging.debug('PaletteWindow.popdown immediate %r', immediate)
+
+ self._popup_anim.stop()
+ self._mouse_detector.stop()
+
+ if not immediate:
+ self._popdown_anim.start()
+ else:
+ self._popdown_anim.stop()
+ self.size_request()
+ self.hide()
+
+ def on_invoker_enter(self):
+ self._popdown_anim.stop()
+ self._mouse_detector.start()
+
+ def on_invoker_leave(self):
+ self._mouse_detector.stop()
+ self.popdown()
+
+ def on_enter(self, event):
+ self._popdown_anim.stop()
+
+ def on_leave(self, event):
+ self.popdown()
+
+ def _invoker_mouse_enter_cb(self, invoker):
+ self.on_invoker_enter()
+
+ def _invoker_mouse_leave_cb(self, invoker):
+ self.on_invoker_leave()
+
+ def _invoker_right_click_cb(self, invoker):
+ self.popup(immediate=True)
+
+ def __enter_notify_event_cb(self, widget, event):
+ if event.detail != gtk.gdk.NOTIFY_INFERIOR and \
+ event.mode == gtk.gdk.CROSSING_NORMAL:
+ self.on_enter(event)
+
+ def __leave_notify_event_cb(self, widget, event):
+ if event.detail != gtk.gdk.NOTIFY_INFERIOR and \
+ event.mode == gtk.gdk.CROSSING_NORMAL:
+ self.on_leave(event)
+
+ def __show_cb(self, widget):
+ if self._invoker is not None:
+ self._invoker.notify_popup()
+
+ self._up = True
+ self.emit('popup')
+
+ def __hide_cb(self, widget):
+ logging.debug('__hide_cb')
+
+ if self._invoker:
+ self._invoker.notify_popdown()
+
+ self._up = False
+ self.emit('popdown')
+
+ def get_rect(self):
+ win_x, win_y = self.window.get_origin()
+ rectangle = self.get_allocation()
+
+ x = win_x + rectangle.x
+ y = win_y + rectangle.y
+ width, height = self.size_request()
+
+ return gtk.gdk.Rectangle(x, y, width, height)
+
+ def get_palette_state(self):
+ return self._palette_state
+
+ def _set_palette_state(self, state):
+ self._palette_state = state
+
+ def set_palette_state(self, state):
+ self._set_palette_state(state)
+
+ palette_state = property(get_palette_state)
+
+
+class _PopupAnimation(animator.Animation):
+
+ def __init__(self, palette):
+ animator.Animation.__init__(self, 0.0, 1.0)
+ self._palette = palette
+
+ def next_frame(self, current):
+ if current == 1.0:
+ self._palette.popup(immediate=True)
+
+
+class _PopdownAnimation(animator.Animation):
+
+ def __init__(self, palette):
+ animator.Animation.__init__(self, 0.0, 1.0)
+ self._palette = palette
+
+ def next_frame(self, current):
+ if current == 1.0:
+ self._palette.popdown(immediate=True)
+
+
+class Invoker(gobject.GObject):
+
+ __gsignals__ = {
+ 'mouse-enter': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'mouse-leave': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'right-click': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'focus-out': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+ ANCHORED = 0
+ AT_CURSOR = 1
+
+ BOTTOM = [(0.0, 0.0, 0.0, 1.0), (-1.0, 0.0, 1.0, 1.0)]
+ RIGHT = [(0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 1.0, 1.0)]
+ TOP = [(0.0, -1.0, 0.0, 0.0), (-1.0, -1.0, 1.0, 0.0)]
+ LEFT = [(-1.0, 0.0, 0.0, 0.0), (-1.0, -1.0, 0.0, 1.0)]
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self.parent = None
+
+ self._screen_area = gtk.gdk.Rectangle(0, 0, gtk.gdk.screen_width(),
+ gtk.gdk.screen_height())
+ self._position_hint = self.ANCHORED
+ self._cursor_x = -1
+ self._cursor_y = -1
+ self._palette = None
+
+ def attach(self, parent):
+ self.parent = parent
+
+ def detach(self):
+ self.parent = None
+ if self._palette is not None:
+ self._palette.destroy()
+ self._palette = None
+
+ def _get_position_for_alignment(self, alignment, palette_dim):
+ palette_halign = alignment[0]
+ palette_valign = alignment[1]
+ invoker_halign = alignment[2]
+ invoker_valign = alignment[3]
+
+ if self._cursor_x == -1 or self._cursor_y == -1:
+ display = gtk.gdk.display_get_default()
+ screen_, x, y, mask_ = display.get_pointer()
+ self._cursor_x = x
+ self._cursor_y = y
+
+ if self._position_hint is self.ANCHORED:
+ rect = self.get_rect()
+ else:
+ dist = style.PALETTE_CURSOR_DISTANCE
+ rect = gtk.gdk.Rectangle(self._cursor_x - dist,
+ self._cursor_y - dist,
+ dist * 2, dist * 2)
+
+ palette_width, palette_height = palette_dim
+
+ x = rect.x + rect.width * invoker_halign + \
+ palette_width * palette_halign
+
+ y = rect.y + rect.height * invoker_valign + \
+ palette_height * palette_valign
+
+ return gtk.gdk.Rectangle(int(x), int(y),
+ palette_width, palette_height)
+
+ def _in_screen(self, rect):
+ return rect.x >= self._screen_area.x and \
+ rect.y >= self._screen_area.y and \
+ rect.x + rect.width <= self._screen_area.width and \
+ rect.y + rect.height <= self._screen_area.height
+
+ def _get_area_in_screen(self, rect):
+ """Return area of rectangle visible in the screen"""
+
+ x1 = max(rect.x, self._screen_area.x)
+ y1 = max(rect.y, self._screen_area.y)
+ x2 = min(rect.x + rect.width,
+ self._screen_area.x + self._screen_area.width)
+ y2 = min(rect.y + rect.height,
+ self._screen_area.y + self._screen_area.height)
+
+ return (x2 - x1) * (y2 - y1)
+
+ def _get_alignments(self):
+ if self._position_hint is self.AT_CURSOR:
+ return [(0.0, 0.0, 1.0, 1.0),
+ (0.0, -1.0, 1.0, 0.0),
+ (-1.0, -1.0, 0.0, 0.0),
+ (-1.0, 0.0, 0.0, 1.0)]
+ else:
+ return self.BOTTOM + self.RIGHT + self.TOP + self.LEFT
+
+ def get_position_for_alignment(self, alignment, palette_dim):
+ rect = self._get_position_for_alignment(alignment, palette_dim)
+ if self._in_screen(rect):
+ return rect
+ else:
+ return None
+
+ def get_position(self, palette_dim):
+ alignment = self.get_alignment(palette_dim)
+ rect = self._get_position_for_alignment(alignment, palette_dim)
+
+ # In case our efforts to find an optimum place inside the screen
+ # failed, just make sure the palette fits inside the screen if at all
+ # possible.
+ rect.x = max(0, rect.x)
+ rect.y = max(0, rect.y)
+
+ rect.x = min(rect.x, self._screen_area.width - rect.width)
+ rect.y = min(rect.y, self._screen_area.height - rect.height)
+
+ return rect
+
+ def get_alignment(self, palette_dim):
+ best_alignment = None
+ best_area = -1
+ for alignment in self._get_alignments():
+ pos = self._get_position_for_alignment(alignment, palette_dim)
+ if self._in_screen(pos):
+ return alignment
+
+ area = self._get_area_in_screen(pos)
+ if area > best_area:
+ best_alignment = alignment
+ best_area = area
+
+ # Palette horiz/vert alignment
+ ph = best_alignment[0]
+ pv = best_alignment[1]
+
+ # Invoker horiz/vert alignment
+ ih = best_alignment[2]
+ iv = best_alignment[3]
+
+ rect = self.get_rect()
+ screen_area = self._screen_area
+
+ if best_alignment in self.LEFT or best_alignment in self.RIGHT:
+ dtop = rect.y - screen_area.y
+ dbottom = screen_area.y + screen_area.height - rect.y - rect.width
+
+ iv = 0
+
+ # Set palette_valign to align to screen on the top
+ if dtop > dbottom:
+ pv = -float(dtop) / palette_dim[1]
+
+ # Set palette_valign to align to screen on the bottom
+ else:
+ pv = -float(palette_dim[1] - dbottom - rect.height) \
+ / palette_dim[1]
+
+ elif best_alignment in self.TOP or best_alignment in self.BOTTOM:
+ dleft = rect.x - screen_area.x
+ dright = screen_area.x + screen_area.width - rect.x - rect.width
+
+ ih = 0
+
+ # Set palette_halign to align to screen on left
+ if dleft > dright:
+ ph = -float(dleft) / palette_dim[0]
+
+ # Set palette_halign to align to screen on right
+ else:
+ ph = -float(palette_dim[0] - dright - rect.width) \
+ / palette_dim[0]
+
+ return (ph, pv, ih, iv)
+
+ def has_rectangle_gap(self):
+ return False
+
+ def draw_rectangle(self, event, palette):
+ pass
+
+ def notify_popup(self):
+ pass
+
+ def notify_popdown(self):
+ self._cursor_x = -1
+ self._cursor_y = -1
+
+ def _ensure_palette_exists(self):
+ if self.parent and self.palette is None:
+ palette = self.parent.create_palette()
+ if palette is not None:
+ self.palette = palette
+
+ def notify_mouse_enter(self):
+ self._ensure_palette_exists()
+ self.emit('mouse-enter')
+
+ def notify_mouse_leave(self):
+ self.emit('mouse-leave')
+
+ def notify_right_click(self):
+ self._ensure_palette_exists()
+ self.emit('right-click')
+
+ def get_palette(self):
+ return self._palette
+
+ def set_palette(self, palette):
+ if self._palette is not None:
+ self._palette.popdown(immediate=True)
+
+ if self._palette:
+ self._palette.props.invoker = None
+
+ self._palette = palette
+
+ if self._palette:
+ self._palette.props.invoker = self
+
+ palette = gobject.property(
+ type=object, setter=set_palette, getter=get_palette)
+
+
+class WidgetInvoker(Invoker):
+
+ def __init__(self, parent=None, widget=None):
+ Invoker.__init__(self)
+
+ self._widget = None
+ self._enter_hid = None
+ self._leave_hid = None
+ self._release_hid = None
+
+ if parent or widget:
+ self.attach_widget(parent, widget)
+
+ def attach_widget(self, parent, widget=None):
+ if widget:
+ self._widget = widget
+ else:
+ self._widget = parent
+
+ self.notify('widget')
+
+ self._enter_hid = self._widget.connect('enter-notify-event',
+ self.__enter_notify_event_cb)
+ self._leave_hid = self._widget.connect('leave-notify-event',
+ self.__leave_notify_event_cb)
+ self._release_hid = self._widget.connect('button-release-event',
+ self.__button_release_event_cb)
+
+ self.attach(parent)
+
+ def detach(self):
+ Invoker.detach(self)
+ self._widget.disconnect(self._enter_hid)
+ self._widget.disconnect(self._leave_hid)
+ self._widget.disconnect(self._release_hid)
+
+ def get_rect(self):
+ allocation = self._widget.get_allocation()
+ if self._widget.window is not None:
+ x, y = self._widget.window.get_origin()
+ else:
+ logging.warning(
+ "Trying to position palette with invoker that's not realized.")
+ x = 0
+ y = 0
+
+ if self._widget.flags() & gtk.NO_WINDOW:
+ x += allocation.x
+ y += allocation.y
+
+ width = allocation.width
+ height = allocation.height
+
+ return gtk.gdk.Rectangle(x, y, width, height)
+
+ def has_rectangle_gap(self):
+ return True
+
+ def draw_rectangle(self, event, palette):
+ if self._widget.flags() & gtk.NO_WINDOW:
+ x, y = self._widget.allocation.x, self._widget.allocation.y
+ else:
+ x = y = 0
+
+ wstyle = self._widget.get_style()
+ gap = _calculate_gap(self.get_rect(), palette.get_rect())
+
+ if gap:
+ wstyle.paint_box_gap(event.window, gtk.STATE_PRELIGHT,
+ gtk.SHADOW_IN, event.area, self._widget,
+ "palette-invoker", x, y,
+ self._widget.allocation.width,
+ self._widget.allocation.height,
+ gap[0], gap[1], gap[2])
+ else:
+ wstyle.paint_box(event.window, gtk.STATE_PRELIGHT,
+ gtk.SHADOW_IN, event.area, self._widget,
+ "palette-invoker", x, y,
+ self._widget.allocation.width,
+ self._widget.allocation.height)
+
+ def __enter_notify_event_cb(self, widget, event):
+ self.notify_mouse_enter()
+
+ def __leave_notify_event_cb(self, widget, event):
+ self.notify_mouse_leave()
+
+ def __button_release_event_cb(self, widget, event):
+ if event.button == 3:
+ self.notify_right_click()
+ return True
+ else:
+ return False
+
+ def get_toplevel(self):
+ return self._widget.get_toplevel()
+
+ def notify_popup(self):
+ Invoker.notify_popup(self)
+ self._widget.queue_draw()
+
+ def notify_popdown(self):
+ Invoker.notify_popdown(self)
+ self._widget.queue_draw()
+
+ def _get_widget(self):
+ return self._widget
+ widget = gobject.property(type=object, getter=_get_widget, setter=None)
+
+
+class CanvasInvoker(Invoker):
+
+ def __init__(self, parent=None):
+ Invoker.__init__(self)
+
+ self._position_hint = self.AT_CURSOR
+ self._motion_hid = None
+ self._release_hid = None
+ self._item = None
+
+ if parent:
+ self.attach(parent)
+
+ def attach(self, parent):
+ Invoker.attach(self, parent)
+
+ self._item = parent
+ self._motion_hid = self._item.connect('motion-notify-event',
+ self.__motion_notify_event_cb)
+ self._release_hid = self._item.connect('button-release-event',
+ self.__button_release_event_cb)
+
+ def detach(self):
+ Invoker.detach(self)
+ self._item.disconnect(self._motion_hid)
+ self._item.disconnect(self._release_hid)
+
+ def get_default_position(self):
+ return self.AT_CURSOR
+
+ def get_rect(self):
+ context = self._item.get_context()
+ if context:
+ x, y = context.translate_to_screen(self._item)
+ width, height = self._item.get_allocation()
+ return gtk.gdk.Rectangle(x, y, width, height)
+ else:
+ return gtk.gdk.Rectangle()
+
+ def __motion_notify_event_cb(self, button, event):
+ if event.detail == hippo.MOTION_DETAIL_ENTER:
+ self.notify_mouse_enter()
+ elif event.detail == hippo.MOTION_DETAIL_LEAVE:
+ self.notify_mouse_leave()
+
+ return False
+
+ def __button_release_event_cb(self, button, event):
+ if event.button == 3:
+ self.notify_right_click()
+ return True
+ else:
+ return False
+
+ def get_toplevel(self):
+ return hippo.get_canvas_for_item(self._item).get_toplevel()
+
+
+class ToolInvoker(WidgetInvoker):
+
+ def __init__(self, parent=None):
+ WidgetInvoker.__init__(self)
+
+ if parent:
+ self.attach_tool(parent)
+
+ def attach_tool(self, widget):
+ self.attach_widget(widget, widget.child)
+
+ def _get_alignments(self):
+ parent = self._widget.get_parent()
+ if parent is None:
+ return WidgetInvoker._get_alignments()
+
+ if parent.get_orientation() is gtk.ORIENTATION_HORIZONTAL:
+ return self.BOTTOM + self.TOP
+ else:
+ return self.LEFT + self.RIGHT
+
+
+class CellRendererInvoker(Invoker):
+
+ def __init__(self):
+ Invoker.__init__(self)
+
+ self._position_hint = self.AT_CURSOR
+ self._tree_view = None
+ self._cell_renderer = None
+ self._motion_hid = None
+ self._leave_hid = None
+ self._release_hid = None
+ self.path = None
+
+ def attach_cell_renderer(self, tree_view, cell_renderer):
+ self._tree_view = tree_view
+ self._cell_renderer = cell_renderer
+
+ self._motion_hid = tree_view.connect('motion-notify-event',
+ self.__motion_notify_event_cb)
+ self._leave_hid = tree_view.connect('leave-notify-event',
+ self.__leave_notify_event_cb)
+ self._release_hid = tree_view.connect('button-release-event',
+ self.__button_release_event_cb)
+
+ self.attach(cell_renderer)
+
+ def detach(self):
+ Invoker.detach(self)
+ self._tree_view.disconnect(self._motion_hid)
+ self._tree_view.disconnect(self._leave_hid)
+ self._tree_view.disconnect(self._release_hid)
+
+ def get_rect(self):
+ allocation = self._tree_view.get_allocation()
+ if self._tree_view.window is not None:
+ x, y = self._tree_view.window.get_origin()
+ else:
+ logging.warning(
+ "Trying to position palette with invoker that's not realized.")
+ x = 0
+ y = 0
+
+ if self._tree_view.flags() & gtk.NO_WINDOW:
+ x += allocation.x
+ y += allocation.y
+
+ width = allocation.width
+ height = allocation.height
+
+ return gtk.gdk.Rectangle(x, y, width, height)
+
+ def __motion_notify_event_cb(self, widget, event):
+ if event.window != widget.get_bin_window():
+ return
+ if self._point_in_cell_renderer(event.x, event.y):
+
+ tree_view = self._tree_view
+ path, column_, x_, y_ = tree_view.get_path_at_pos(int(event.x),
+ int(event.y))
+ if path != self.path:
+ if self.path is not None:
+ self._redraw_path(self.path)
+ if path is not None:
+ self._redraw_path(path)
+ if self.palette is not None:
+ self.palette.popdown(immediate=True)
+ self.palette = None
+ self.path = path
+
+ self.notify_mouse_enter()
+ else:
+ if self.path is not None:
+ self._redraw_path(self.path)
+ self.path = None
+ self.notify_mouse_leave()
+
+ def _redraw_path(self, path):
+ for column in self._tree_view.get_columns():
+ if self._cell_renderer in column.get_cell_renderers():
+ break
+ area = self._tree_view.get_background_area(path, column)
+ x, y = \
+ self._tree_view.convert_bin_window_to_widget_coords(area.x, area.y)
+ self._tree_view.queue_draw_area(x, y, area.width, area.height)
+
+ def __leave_notify_event_cb(self, widget, event):
+ self.notify_mouse_leave()
+
+ def __button_release_event_cb(self, widget, event):
+ if event.button == 1 and self._point_in_cell_renderer(event.x,
+ event.y):
+ tree_view = self._tree_view
+ path, column_, x_, y_ = tree_view.get_path_at_pos(int(event.x),
+ int(event.y))
+ self._cell_renderer.emit('clicked', path)
+ # So the treeview receives it and knows a drag isn't going on
+ return False
+ if event.button == 3 and self._point_in_cell_renderer(event.x,
+ event.y):
+ self.notify_right_click()
+ return True
+ else:
+ return False
+
+ def _point_in_cell_renderer(self, event_x, event_y):
+ pos = self._tree_view.get_path_at_pos(int(event_x), int(event_y))
+ if pos is None:
+ return False
+
+ path_, column, x, y_ = pos
+
+ for cell_renderer in column.get_cell_renderers():
+ if cell_renderer == self._cell_renderer:
+ cell_x, cell_width = column.cell_get_position(cell_renderer)
+ if x > cell_x and x < (cell_x + cell_width):
+ return True
+ return False
+
+ return False
+
+ def get_toplevel(self):
+ return self._tree_view.get_toplevel()
+
+ def notify_popup(self):
+ Invoker.notify_popup(self)
+
+ def notify_popdown(self):
+ Invoker.notify_popdown(self)
+ self.palette = None
+
+ def get_default_position(self):
+ return self.AT_CURSOR
diff --git a/toolkit/json.py b/toolkit/json.py
new file mode 100644
index 0000000..a8cbcbd
--- /dev/null
+++ b/toolkit/json.py
@@ -0,0 +1,35 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# 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
+
+"""
+Unify usage of simplejson in Python 2.5/2.6
+
+In Python 2.5 it imports simplejson module, in 2.6 native json module.
+
+Usage:
+
+ import toolkit.json as json
+
+ # and using regular simplejson interface with module json
+ json.dumps([])
+
+"""
+
+try:
+ from json import *
+ dumps
+except (ImportError, NameError):
+ from simplejson import *
diff --git a/toolkit/pixbuf.py b/toolkit/pixbuf.py
new file mode 100644
index 0000000..c3bb7d1
--- /dev/null
+++ b/toolkit/pixbuf.py
@@ -0,0 +1,116 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# 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
+
+"""gtk.gdk.Pixbuf extensions"""
+
+import re
+import cStringIO
+import gtk
+import rsvg
+import cairo
+import logging
+
+from sugar.graphics import style
+from sugar.graphics.xocolor import XoColor, is_valid
+from sugar.util import LRU
+
+
+def to_file(pixbuf):
+ """Convert pixbuf object to file object"""
+
+ def push(pixbuf, buffer):
+ buffer.write(pixbuf)
+
+ buffer = cStringIO.StringIO()
+ pixbuf.save_to_callback(push, 'png', user_data=buffer)
+ buffer.seek(0)
+
+ return buffer
+
+def to_str(pixbuf):
+ """Convert pixbuf object to string"""
+ return to_file(pixbuf).getvalue()
+
+def from_str(str):
+ """Convert string to pixbuf object"""
+
+ loader = gtk.gdk.pixbuf_loader_new_with_mime_type('image/png')
+
+ try:
+ loader.write(str)
+ except Exception, e:
+ logging.error('pixbuf.from_str: %s' % e)
+ return None
+ finally:
+ loader.close()
+
+ return loader.get_pixbuf()
+
+
+def at_size_with_ratio(pixbuf, width, height, type=gtk.gdk.INTERP_BILINEAR):
+ image_width = pixbuf.get_width()
+ image_height = pixbuf.get_height()
+
+ ratio_width = float(width) / image_width
+ ratio_height = float(height) / image_height
+ ratio = min(ratio_width, ratio_height)
+
+ if ratio_width != ratio:
+ ratio_width = ratio
+ width = int(image_width * ratio)
+ elif ratio_height != ratio:
+ ratio_height = ratio
+ height = int(image_height * ratio)
+
+ return pixbuf.scale_simple(width, height, type)
+
+def from_svg_at_size(filename=None, width=None, height=None, handle=None,
+ keep_ratio=True):
+ """Scale and load SVG into pixbuf"""
+
+ if not handle:
+ handle = rsvg.Handle(filename)
+
+ dimensions = handle.get_dimension_data()
+ icon_width = dimensions[0]
+ icon_height = dimensions[1]
+
+ if icon_width != width or icon_height != height:
+ ratio_width = float(width) / icon_width
+ ratio_height = float(height) / icon_height
+
+ if keep_ratio:
+ ratio = min(ratio_width, ratio_height)
+ if ratio_width != ratio:
+ ratio_width = ratio
+ width = int(icon_width * ratio)
+ elif ratio_height != ratio:
+ ratio_height = ratio
+ height = int(icon_height * ratio)
+ else:
+ ratio_width = 1
+ ratio_height = 1
+
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+ context = cairo.Context(surface)
+ context.scale(ratio_width, ratio_height)
+ handle.render_cairo(context)
+
+ loader = gtk.gdk.pixbuf_loader_new_with_mime_type('image/png')
+ surface.write_to_png(loader)
+ loader.close()
+
+ return loader.get_pixbuf()
diff --git a/toolkit/radiopalette.py b/toolkit/radiopalette.py
new file mode 100644
index 0000000..9c902b1
--- /dev/null
+++ b/toolkit/radiopalette.py
@@ -0,0 +1,109 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gtk
+
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.palette import Palette
+
+
+class RadioMenuButton(ToolButton):
+
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, **kwargs)
+ self.selected_button = None
+
+ if self.props.palette:
+ self.__palette_cb(None, None)
+
+ self.connect('clicked', self.__clicked_cb)
+ self.connect('notify::palette', self.__palette_cb)
+
+ def _do_clicked(self):
+ if self.palette is None:
+ return
+ if self.palette.is_up() and \
+ self.palette.palette_state == Palette.SECONDARY:
+ self.palette.popdown(immediate=True)
+ else:
+ self.palette.popup(immediate=True)
+ self.palette.props.invoker.emit('right-click')
+
+ def __palette_cb(self, widget, pspec):
+ if not isinstance(self.props.palette, RadioPalette):
+ return
+ self.props.palette.update_button()
+
+ def __clicked_cb(self, button):
+ self._do_clicked()
+
+
+class RadioToolsButton(RadioMenuButton):
+
+ def __init__(self, **kwargs):
+ RadioMenuButton.__init__(self, **kwargs)
+
+ def _do_clicked(self):
+ if not self.selected_button:
+ return
+ self.selected_button.emit('clicked')
+
+
+class RadioPalette(Palette):
+
+ def __init__(self, **kwargs):
+ Palette.__init__(self, **kwargs)
+
+ self.button_box = gtk.HBox()
+ self.button_box.show()
+ self.set_content(self.button_box)
+
+ def append(self, button, label):
+ children = self.button_box.get_children()
+
+ if button.palette is not None:
+ raise RuntimeError("Palette's button should not have sub-palettes")
+
+ button.show()
+ button.connect('clicked', self.__clicked_cb)
+ self.button_box.pack_start(button, fill=False)
+ button.palette_label = label
+
+ if not children:
+ self.__clicked_cb(button)
+
+ def update_button(self):
+ for i in self.button_box.get_children():
+ self.__clicked_cb(i)
+
+ def __clicked_cb(self, button):
+ if not button.get_active():
+ return
+
+ self.set_primary_text(button.palette_label)
+ self.popdown(immediate=True)
+
+ if self.props.invoker is not None:
+ parent = self.props.invoker.parent
+ else:
+ parent = None
+ if not isinstance(parent, RadioMenuButton):
+ return
+
+ parent.props.label = button.palette_label
+ parent.set_icon(button.props.icon_name)
+ parent.selected_button = button
diff --git a/toolkit/scrolledbox.py b/toolkit/scrolledbox.py
new file mode 100644
index 0000000..ead071e
--- /dev/null
+++ b/toolkit/scrolledbox.py
@@ -0,0 +1,191 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# 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 sugar.graphics.icon import Icon
+
+class ScrollButton(gtk.ToolButton):
+ def __init__(self, icon_name):
+ gtk.ToolButton.__init__(self)
+
+ icon = Icon(icon_name = icon_name,
+ icon_size=gtk.ICON_SIZE_SMALL_TOOLBAR)
+ # The alignment is a hack to work around gtk.ToolButton code
+ # that sets the icon_size when the icon_widget is a gtk.Image
+ alignment = gtk.Alignment(0.5, 0.5)
+ alignment.add(icon)
+ self.set_icon_widget(alignment)
+
+class ScrolledBox(gtk.EventBox):
+ def __init__(self, orientation,
+ arrows_policy=gtk.POLICY_AUTOMATIC,
+ scroll_policy=gtk.POLICY_AUTOMATIC):
+
+ gtk.EventBox.__init__(self)
+ self.orientation = orientation
+ self._viewport = None
+ self._abox = None
+ self._aviewport = None
+ self._aviewport_sig = None
+ self._arrows_policy = arrows_policy
+ self._scroll_policy = scroll_policy
+ self._left = None
+ self._right = None
+
+ if orientation == gtk.ORIENTATION_HORIZONTAL:
+ box = gtk.HBox()
+ else:
+ box = gtk.VBox()
+ if self._arrows_policy == gtk.POLICY_AUTOMATIC:
+ box.connect("size-allocate", self._box_allocate_cb)
+ self.add(box)
+
+ if self._arrows_policy != gtk.POLICY_NEVER:
+ if orientation == gtk.ORIENTATION_HORIZONTAL:
+ self._left = ScrollButton('go-left')
+ else:
+ self._left = ScrollButton('go-up')
+ self._left.connect('clicked', self._scroll_cb,
+ gtk.gdk.SCROLL_LEFT)
+ box.pack_start(self._left, False, False, 0)
+
+ self._scrolled = gtk.ScrolledWindow()
+ if orientation == gtk.ORIENTATION_HORIZONTAL:
+ self._scrolled.set_policy(scroll_policy, gtk.POLICY_NEVER)
+ else:
+ self._scrolled.set_policy(gtk.POLICY_NEVER, scroll_policy)
+ self._scrolled.connect('scroll-event', self._scroll_event_cb)
+ box.pack_start(self._scrolled, True, True, 0)
+
+ if orientation == gtk.ORIENTATION_HORIZONTAL:
+ self._adj = self._scrolled.get_hadjustment()
+ else:
+ self._adj = self._scrolled.get_vadjustment()
+ self._adj.connect('changed', self._scroll_changed_cb)
+ self._adj.connect('value-changed', self._scroll_changed_cb)
+
+ if self._arrows_policy != gtk.POLICY_NEVER:
+ if orientation == gtk.ORIENTATION_HORIZONTAL:
+ self._right = ScrollButton('go-right')
+ else:
+ self._right = ScrollButton('go-down')
+ self._right.connect('clicked', self._scroll_cb,
+ gtk.gdk.SCROLL_RIGHT)
+ box.pack_start(self._right, False, False, 0)
+
+ def modify_fg(self, state, bg):
+ gtk.EventBox.modify_fg(self, state, bg)
+ self._viewport.get_parent().modify_fg(state, bg)
+
+ def modify_bg(self, state, bg):
+ gtk.EventBox.modify_bg(self, state, bg)
+ self._viewport.get_parent().modify_bg(state, bg)
+
+ def set_viewport(self, widget):
+ if widget == self._viewport: return
+ if self._viewport and self._aviewport_sig:
+ self._viewport.disconnect(self._aviewport_sig)
+ self._viewport = widget
+
+ if self._arrows_policy == gtk.POLICY_AUTOMATIC:
+ self._aviewport_sig = self._viewport.connect('size-allocate',
+ self._viewport_allocate_cb)
+
+ self._scrolled.add_with_viewport(widget)
+
+ def get_viewport_allocation(self):
+ alloc = self._scrolled.get_allocation()
+ alloc.x -= self._adj.get_value()
+ return alloc
+
+ def get_adjustment(self):
+ return self._adj
+
+ def _box_allocate_cb(self, w, a):
+ self._abox = a
+ self._update_arrows()
+
+ def _viewport_allocate_cb(self, w, a):
+ self._aviewport = a
+ self._update_arrows()
+
+ def _update_arrows(self):
+ if not self._abox or not self._aviewport: return
+
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ show_flag = self._abox.width < self._aviewport.width
+ else:
+ show_flag = self._abox.height < self._aviewport.height
+
+ if show_flag:
+ self._left.show()
+ self._right.show()
+ else:
+ self._left.hide()
+ self._right.hide()
+
+ def _scroll_event_cb(self, widget, event):
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ if event.direction == gtk.gdk.SCROLL_UP:
+ event.direction = gtk.gdk.SCROLL_LEFT
+ if event.direction == gtk.gdk.SCROLL_DOWN:
+ event.direction = gtk.gdk.SCROLL_RIGHT
+ else:
+ if event.direction == gtk.gdk.SCROLL_LEFT:
+ event.direction = gtk.gdk.SCROLL_UP
+ if event.direction == gtk.gdk.SCROLL_RIGHT:
+ event.direction = gtk.gdk.SCROLL_DOWN
+
+ if self._scroll_policy == gtk.POLICY_NEVER:
+ self._scroll_cb(None, event.direction)
+
+ return False
+
+ def _scroll_cb(self, widget, direction):
+ if direction in (gtk.gdk.SCROLL_LEFT, gtk.gdk.SCROLL_UP):
+ val = max(self._adj.get_property('lower'), self._adj.get_value()
+ - self._adj.get_property('page_increment'))
+ else:
+ val = min(self._adj.get_property('upper')
+ - self._adj.get_property('page_size'),
+ self._adj.get_value()
+ + self._adj.get_property('page_increment'))
+
+ self._adj.set_value(val)
+
+ def _scroll_changed_cb(self, widget):
+ val = self._adj.get_value()
+ if self._left:
+ if val == 0:
+ self._left.set_sensitive(False)
+ else:
+ self._left.set_sensitive(True)
+
+ if self._right:
+ if val >= self._adj.get_property('upper') - \
+ self._adj.get_property('page_size'):
+ self._right.set_sensitive(False)
+ else:
+ self._right.set_sensitive(True)
+
+class HScrolledBox(ScrolledBox):
+ def __init__(self, **kwargs):
+ ScrolledBox.__init__(self, gtk.ORIENTATION_HORIZONTAL, **kwargs)
+
+class VScrolledBox(ScrolledBox):
+ def __init__(self, **kwargs):
+ ScrolledBox.__init__(self, gtk.ORIENTATION_VERTICAL, **kwargs)
diff --git a/toolkit/tarball.py b/toolkit/tarball.py
new file mode 100644
index 0000000..0a4a1b2
--- /dev/null
+++ b/toolkit/tarball.py
@@ -0,0 +1,125 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# 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
+
+"""Simplify tarfile module usage"""
+
+import os
+import time
+import tarfile
+import cStringIO
+import gtk
+import zipfile
+import tempfile
+import shutil
+
+
+class TarballError(Exception):
+ """Base Tarball exception."""
+ pass
+
+
+class BadDataTypeError(TarballError):
+ """Exception for unsupported data type in read/write methods."""
+ pass
+
+
+class Tarball:
+ """
+ Wrap standart tarfile module to simplify read/write operations.
+ In read mode Tarball can load zip files as well.
+
+ Write usage:
+
+ # create Tarball object
+ # to see all supported modes use
+ # http://docs.python.org/library/tarfile.html#tarfile.open
+ tar = Tarball(tarfile, 'w')
+
+ # write string to file in tarball
+ tar.write('name within tarball', 'string to write')
+
+ # save and close tarball file
+ tar.close()
+
+ Read usage:
+
+ # create Tarball object
+ tar = Tarball(tarfile)
+
+ # read content of file in tarball to string
+ str_content = tar.read('name within tarball')
+ """
+
+ def __init__(self, name=None, mode='r', mtime=None):
+ if not mode.startswith('r') or tarfile.is_tarfile(name):
+ self.__tar = tarfile.TarFile(name=name, mode=mode)
+ else:
+ # convert for tar
+
+ if not zipfile.is_zipfile(name):
+ raise tarfile.ReadError()
+
+ try:
+ tmp_dir = tempfile.mkdtemp()
+ tmp_fd, tmp_name = tempfile.mkstemp()
+ tmp_fo = os.fdopen(tmp_fd, 'w')
+
+ zip = zipfile.ZipFile(name)
+ zip.extractall(tmp_dir)
+
+ tar = tarfile.TarFile(fileobj=tmp_fo, mode='w')
+ tar.add(tmp_dir, arcname='')
+ tar.close()
+
+ self.__tar = tarfile.TarFile(name=tmp_name, mode=mode)
+ finally:
+ tmp_fo.close()
+ os.unlink(tmp_name)
+ shutil.rmtree(tmp_dir)
+
+ if mtime:
+ self.mtime = mtime
+ else:
+ self.mtime = time.time()
+
+ def close(self):
+ """Save(if 'r' mode was given) and close tarball file."""
+ self.__tar.close()
+
+ def getnames(self):
+ """Return names of members sorted by creation order."""
+ return self.__tar.getnames()
+
+ def read(self, arcname):
+ """Returns sring with content of given file from tarball."""
+ file_o = self.__tar.extractfile(arcname.encode('utf8'))
+ if not file_o:
+ return None
+ out = file_o.read()
+ file_o.close()
+ return out
+
+ def write(self, arcname, data, mode=0644):
+ """
+ Stores given object to file in tarball.
+ Raises BadDataTypeError exception If data type isn't supported.
+ """
+ info = tarfile.TarInfo(arcname.encode('utf8'))
+ info.mode = mode
+ info.mtime = self.mtime
+ info.size = len(data)
+
+ self.__tar.addfile(info, cStringIO.StringIO(data))
diff --git a/toolkit/temposlider.py b/toolkit/temposlider.py
new file mode 100644
index 0000000..8fcf8cb
--- /dev/null
+++ b/toolkit/temposlider.py
@@ -0,0 +1,211 @@
+# Copyright (C) 2006-2008, TamTam Team
+#
+# 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
+
+# Widget was copy&pasted from TamTam activities
+
+import gtk
+import rsvg
+import cairo
+
+from sugar.graphics import style
+
+class TempoSlider(gtk.HBox):
+ def __init__(self, min_value, max_value):
+ gtk.HBox.__init__(self)
+
+ self._pixbuf = [None] * 8
+ self._image = gtk.Image()
+ self._image.show()
+
+ # used to store tempo updates while the slider is active
+ self._delayed = 0
+ self._active = False
+
+ self.adjustment = gtk.Adjustment(min_value, min_value, max_value,
+ (max_value - min_value) / 8, (max_value - min_value) / 8, 0)
+ self._adjustment_h = self.adjustment.connect('value-changed',
+ self._changed_cb)
+
+ slider = gtk.HScale(adjustment = self.adjustment)
+ slider.show()
+ slider.set_draw_value(False)
+ slider.connect("button-press-event", self._press_cb)
+ slider.connect("button-release-event", self._release_cb)
+
+ self.pack_start(slider, True, True)
+ self.pack_end(self._image, False, False)
+
+ def set_value(self, tempo, quiet = False):
+ if self._active:
+ self._delayed = tempo
+ elif quiet:
+ self.adjustment.handler_block(self._adjustment_h)
+ self.adjustment.set_value(tempo)
+ self._update(tempo)
+ self.adjustment.handler_unblock(self._adjustment_h)
+ else:
+ self.adjustment.set_value(tempo)
+
+ def _changed_cb(self, widget):
+ self._update(widget.get_value())
+
+ def _update(self, tempo):
+ def map_range(value, ilower, iupper, olower, oupper):
+ if value == iupper:
+ return oupper
+ return olower + int((oupper-olower+1) * (value-ilower) /
+ float(iupper-ilower))
+
+ img = map_range(tempo, self.adjustment.lower,
+ self.adjustment.upper, 0, 7)
+
+ if not self._pixbuf[img]:
+ svg = rsvg.Handle(data=IMAGE[img])
+ self._pixbuf[img] = _from_svg_at_size(handle=svg,
+ width=style.STANDARD_ICON_SIZE,
+ height=style.STANDARD_ICON_SIZE)
+
+ self._image.set_from_pixbuf(self._pixbuf[img])
+
+ def _press_cb(self, widget, event):
+ self._active = True
+
+ def _release_cb(self, widget, event):
+ self._active = False
+ if self._delayed != 0:
+ self.set_value(self._delayed, True)
+ self._delayed = 0
+
+def _from_svg_at_size(filename=None, width=None, height=None, handle=None,
+ keep_ratio=True):
+ """ import from pixbuf.py """
+
+ if not handle:
+ handle = rsvg.Handle(filename)
+
+ dimensions = handle.get_dimension_data()
+ icon_width = dimensions[0]
+ icon_height = dimensions[1]
+
+ if icon_width != width or icon_height != height:
+ ratio_width = float(width) / icon_width
+ ratio_height = float(height) / icon_height
+
+ if keep_ratio:
+ ratio = min(ratio_width, ratio_height)
+ if ratio_width != ratio:
+ ratio_width = ratio
+ width = int(icon_width * ratio)
+ elif ratio_height != ratio:
+ ratio_height = ratio
+ height = int(icon_height * ratio)
+ else:
+ ratio_width = 1
+ ratio_height = 1
+
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+ context = cairo.Context(surface)
+ context.scale(ratio_width, ratio_height)
+ handle.render_cairo(context)
+
+ loader = gtk.gdk.pixbuf_loader_new_with_mime_type('image/png')
+ surface.write_to_png(loader)
+ loader.close()
+
+ return loader.get_pixbuf()
+
+IMAGE = [None] * 8
+
+IMAGE[0] = """<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M23.5,6.5c3,3,7,7,9,11c-7,5-4,6-3,26c-1,1-8,1-9,0c0,0,2,1,2-1
+ c0-3-2-7-2-11c0-2,1-4,1-6c0-3-2-1-2-3c0-3,3-8,3-11c0-2-1-1-2-2v-3H23.5z"/>
+</svg>
+"""
+
+IMAGE[1] = """<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M27.5,44.5v-3C28.5,42.5,28.5,43.5,27.5,44.5z M26.5,10.5
+ c2,2,2,6,2,8c0,4-3,11-3,13s4,7,7,10c-2,2-4,3-5,5h-6c1-1,2-3,2-5c0-3-2-9-3-14c0,0,0-1-1,0v-6c0-3,3-8,3-11c0-1-2-2-2-6h3
+ C23.5,5.5,26.5,9.5,26.5,10.5z"/>
+</svg>
+"""
+
+IMAGE[2] = """<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M30.5,17.5c0,3-2,2-2,4c0,3,4,14,7,21c-1,0-3,1-5,1c1-1,2,0,2-3
+ c0-2-4-7-6-10c-3,3-5,8-7,13c-1,0-3-1-4-1c3-3,7-14,7-18s-1-3-4-4c3-2,4-8,4-14h3C23.5,9.5,30.5,14.5,30.5,17.5z"/>
+</svg>
+"""
+
+IMAGE[3] = """<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M34.5,22.5c-1-1-2-4-5-6c-1,2,0,3,0,6c0,2-3,4-3,7c0,2,4,2,4,4
+ c0,3-1,4-2,5c0-1,0-3-1-4c-1,3-2,7-3,10c-4-3,0-6,0-9s-3-11-4-17l-4,4c1-5,8.25-11.12,7.25-16.12c0.68,0.68,3.029,0,2.87,2.12
+ C26.5,10.25,33.62,17.75,34.5,22.5z"/>
+</svg>
+"""
+
+IMAGE[4] = """<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M24.5,13.5c2,1,5,3,5,6c0,2-2,3-2,5c0,9,11,4,11,13c-1,0-3-2-4-3
+ c-3-1-9,1-10-3c-2,3-5,7-7,11c-3,0-3-1-4-1c0-2,3-3,4-6s4-8,4-10c0-3-1-3-2-5c-1,0-2,1-3,2c0-1,2-3,2-4c1-2,3-5,2-8c0,0,1-1,4-2
+ C25.5,9.5,25.5,11.5,24.5,13.5z"/>
+</svg>
+"""
+
+IMAGE[5] = """<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M22.5,10.5c3,2,7,5,7,7c0,3-4,8-4,10c0,3,1,3,1,5h5l2-2l2,2v4
+ c-1,0-3-2-5-2c-3,0-5,1-8,1c-1,3-2,7-2,10h-5c1-1,3-3,3-4c1-5,1-11,1-18l-1-1c-1,1-1.75,2.88-2.75,2.88c0,0-0.25-0.63-0.25-1.63
+ c4-4,2-8.25,2-13.25c0-1,0.25-2.5,0.38-5.38L22.5,5.5C23.12,6.5,22.5,8.5,22.5,10.5z"/>
+<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#333333" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" points="
+ 25,20 25.25,16.75 26.5,17.88 "/>
+</svg>
+"""
+
+IMAGE[6] = """<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M20.5,7.5c1,1,1,3,1,4c10,4,8,6,8,14c0,2,6,9,10,13c-1,2-2,4-4,5
+ c1.62-8.88-8.75-13.88-12-15c-1,1-1,0-1,2c0,3,2,5,3,7c-1,1-3,2-6,2c0-1,2-1,2-4c0-2-4-4-4-6c0-3,3-4,5-6c-3-8-8-2-11-6h6
+ c0-1,1,0,1-3c0-2-1-1-2-2l1-5H20.5z"/>
+</svg>
+"""
+
+IMAGE[7] = """<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M20.5,12.5c0.67,0.4,0.4,1.9,1.75,2.25s1.05-0.38,1.5-0.37
+ c4.971,0,10.95-0.88,11.75,7.12c-1-2-3-4-5-5l-4,1c1,2,4,4,5,7c1,1,1,4,1,6c3,3,8-1,11,6c-2.88-0.82-4.25-2.62-12.75-2.75
+ c-1.561-0.02-2.34-1.561-3.75-1.87c-3.42-0.76-4.67-0.38-5.5-0.38c-3,0-8,7-11,7c-2,0-3-1-3-2c4,2,8-4,9-7c2-1,5-1,8-3c-2-4-6-5-8-3
+ l-6-6l2-2c1,1,1,2,1,4c1,0,4.12,0.38,6.12-0.62L16.5,17.5v-5H20.5z"/>
+</svg>
+"""
diff --git a/toolkit/toolbarbox.py b/toolkit/toolbarbox.py
new file mode 100644
index 0000000..7172b8b
--- /dev/null
+++ b/toolkit/toolbarbox.py
@@ -0,0 +1,333 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gtk
+import gobject
+
+from sugar.graphics import style
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics import palettegroup
+
+from toolkit.internals.palettewindow import PaletteWindow
+from toolkit.internals.palettewindow import ToolInvoker
+
+
+_ARROW_SIZE = style.zoom(24)
+_LINE_WIDTH = 2
+
+class ToolbarButton(ToolButton):
+
+ def __init__(self, page=None, **kwargs):
+ ToolButton.__init__(self, **kwargs)
+
+ self.page_widget = None
+
+ self.set_page(page)
+
+ self.connect('clicked',
+ lambda widget: self.set_expanded(not self.is_expanded()))
+ self.connect('size-allocate', self.__size_allocate_cb)
+
+ def get_toolbar_box(self):
+ if not hasattr(self.parent, 'owner'):
+ return None
+ return self.parent.owner
+
+ toolbar_box = property(get_toolbar_box)
+
+ def get_page(self):
+ if self.page_widget is None:
+ return None
+ return _get_embedded_page(self.page_widget)
+
+ def set_page(self, page):
+ if page is None:
+ self.page_widget = None
+ return
+
+ self.page_widget, alignment_ = _embed_page(_Box, page)
+ w_, h = gtk.icon_size_lookup(gtk.ICON_SIZE_LARGE_TOOLBAR)
+ page.show()
+
+ if self.props.palette is None:
+ self.props.palette = _ToolbarPalette(invoker=ToolInvoker(self))
+ self._move_page_to_palette()
+
+ page = gobject.property(type=object, getter=get_page, setter=set_page)
+
+ def is_in_palette(self):
+ return self.page is not None and \
+ self.page_widget.parent == self.props.palette
+
+ def is_expanded(self):
+ return self.page is not None and \
+ not self.is_in_palette()
+
+ def popdown(self):
+ if self.props.palette is not None:
+ self.props.palette.popdown(immediate=True)
+
+ def set_expanded(self, expanded):
+ self.popdown()
+
+ if self.page is None or self.is_expanded() == expanded:
+ return
+
+ if not expanded:
+ self._move_page_to_palette()
+ return
+
+ box = self.toolbar_box
+
+ if box.expanded_button is not None:
+ if box.expanded_button.window is not None:
+ # need to redraw it to erase arrow
+ box.expanded_button.window.invalidate_rect(None, True)
+ box.expanded_button.set_expanded(False)
+ box.expanded_button = self
+
+ self._unparent()
+
+ self.modify_bg(gtk.STATE_NORMAL, box.background)
+ _setup_page(self.page_widget, box.background, box.props.padding)
+ box.pack_start(self.page_widget)
+
+ def _move_page_to_palette(self):
+ if self.is_in_palette():
+ return
+
+ self._unparent()
+
+ if isinstance(self.props.palette, _ToolbarPalette):
+ self.props.palette.add(self.page_widget)
+
+ def _unparent(self):
+ if self.page_widget.parent is None:
+ return
+ self.page_widget.parent.remove(self.page_widget)
+
+ def do_expose_event(self, event):
+ if not self.is_expanded() or self.props.palette is not None and \
+ self.props.palette.is_up():
+ ToolButton.do_expose_event(self, event)
+ _paint_arrow(self, event, gtk.ARROW_DOWN)
+ return
+
+ alloc = self.allocation
+
+ self.get_style().paint_box(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_IN, event.area, self,
+ 'palette-invoker', alloc.x, 0,
+ alloc.width, alloc.height + _LINE_WIDTH)
+
+ if self.child.state != gtk.STATE_PRELIGHT:
+ self.get_style().paint_box(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, self, None,
+ alloc.x + _LINE_WIDTH, _LINE_WIDTH,
+ alloc.width - _LINE_WIDTH * 2, alloc.height)
+
+ gtk.ToolButton.do_expose_event(self, event)
+ _paint_arrow(self, event, gtk.ARROW_UP)
+
+ def __size_allocate_cb(self, button, allocation):
+ if self.page_widget is not None:
+ self.page_widget.set_size_request(-1, allocation.height)
+
+
+class ToolbarBox(gtk.VBox):
+
+ def __init__(self, padding=style.TOOLBOX_HORIZONTAL_PADDING):
+ gtk.VBox.__init__(self)
+ self._expanded_button_index = -1
+ self.background = None
+
+ self._toolbar = gtk.Toolbar()
+ self._toolbar.owner = self
+ self._toolbar.connect('remove', self.__remove_cb)
+
+ self._toolbar_widget, self._toolbar_alignment = \
+ _embed_page(gtk.EventBox, self._toolbar)
+ self.pack_start(self._toolbar_widget)
+
+ self.props.padding = padding
+ self.modify_bg(gtk.STATE_NORMAL,
+ style.COLOR_TOOLBAR_GREY.get_gdk_color())
+
+ def get_toolbar(self):
+ return self._toolbar
+
+ toolbar = property(get_toolbar)
+
+ def get_expanded_button(self):
+ if self._expanded_button_index == -1:
+ return None
+ return self.toolbar.get_nth_item(self._expanded_button_index)
+
+ def set_expanded_button(self, button):
+ if not button in self.toolbar:
+ self._expanded_button_index = -1
+ return
+ self._expanded_button_index = self.toolbar.get_item_index(button)
+
+ expanded_button = property(get_expanded_button, set_expanded_button)
+
+ def get_padding(self):
+ return self._toolbar_alignment.props.left_padding
+
+ def set_padding(self, pad):
+ self._toolbar_alignment.set_padding(0, 0, pad, pad)
+
+ padding = gobject.property(type=object,
+ getter=get_padding, setter=set_padding)
+
+ def modify_bg(self, state, color):
+ if state == gtk.STATE_NORMAL:
+ self.background = color
+ self._toolbar_widget.modify_bg(state, color)
+ self.toolbar.modify_bg(state, color)
+
+ def __remove_cb(self, sender, button):
+ if not isinstance(button, ToolbarButton):
+ return
+ button.popdown()
+ if button == self.expanded_button:
+ self.remove(button.page_widget)
+ self._expanded_button_index = -1
+
+
+class _ToolbarPalette(PaletteWindow):
+
+ def __init__(self, **kwargs):
+ PaletteWindow.__init__(self, **kwargs)
+ self.set_border_width(0)
+ self._has_focus = False
+
+ group = palettegroup.get_group('default')
+ group.connect('popdown', self.__group_popdown_cb)
+ self.set_group_id('toolbarbox')
+
+ def get_expanded_button(self):
+ return self.invoker.parent
+
+ expanded_button = property(get_expanded_button)
+
+ def on_invoker_enter(self):
+ PaletteWindow.on_invoker_enter(self)
+ self._set_focus(True)
+
+ def on_invoker_leave(self):
+ PaletteWindow.on_invoker_leave(self)
+ self._set_focus(False)
+
+ def on_enter(self, event):
+ PaletteWindow.on_enter(self, event)
+ self._set_focus(True)
+
+ def on_leave(self, event):
+ PaletteWindow.on_enter(self, event)
+ self._set_focus(False)
+
+ def _set_focus(self, new_focus):
+ self._has_focus = new_focus
+ if not self._has_focus:
+ group = palettegroup.get_group('default')
+ if not group.is_up():
+ self.popdown()
+
+ def do_size_request(self, requisition):
+ gtk.Window.do_size_request(self, requisition)
+ requisition.width = max(requisition.width,
+ gtk.gdk.screen_width())
+
+ def popup(self, immediate=False):
+ button = self.expanded_button
+ if button.is_expanded():
+ return
+ box = button.toolbar_box
+ _setup_page(button.page_widget, style.COLOR_BLACK.get_gdk_color(),
+ box.props.padding)
+ PaletteWindow.popup(self, immediate)
+
+ def __group_popdown_cb(self, group):
+ if not self._has_focus:
+ self.popdown(immediate=True)
+
+
+class _Box(gtk.EventBox):
+
+ def __init__(self):
+ gtk.EventBox.__init__(self)
+ self.connect('expose-event', self.do_expose_event)
+ self.set_app_paintable(True)
+
+ def do_expose_event(self, widget, event):
+ if self.parent.expanded_button is None:
+ return
+ alloc = self.parent.expanded_button.allocation
+ self.get_style().paint_box(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_IN, event.area, self,
+ 'palette-invoker', -_LINE_WIDTH, 0,
+ self.allocation.width + _LINE_WIDTH * 2,
+ self.allocation.height + _LINE_WIDTH)
+ self.get_style().paint_box(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, self, None,
+ alloc.x + _LINE_WIDTH, 0,
+ alloc.width - _LINE_WIDTH * 2, _LINE_WIDTH)
+
+
+def _setup_page(page_widget, color, hpad):
+ vpad = _LINE_WIDTH
+ page_widget.child.set_padding(vpad, vpad, hpad, hpad)
+
+ page = _get_embedded_page(page_widget)
+ page.modify_bg(gtk.STATE_NORMAL, color)
+ if isinstance(page, gtk.Container):
+ for i in page.get_children():
+ i.modify_bg(gtk.STATE_INSENSITIVE, color)
+
+ page_widget.modify_bg(gtk.STATE_NORMAL, color)
+ page_widget.modify_bg(gtk.STATE_PRELIGHT, color)
+
+
+def _embed_page(box_class, page):
+ page.show()
+
+ alignment = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
+ alignment.add(page)
+ alignment.show()
+
+ page_widget = box_class()
+ page_widget.modify_bg(gtk.STATE_ACTIVE,
+ style.COLOR_BUTTON_GREY.get_gdk_color())
+ page_widget.add(alignment)
+ page_widget.show()
+
+ return (page_widget, alignment)
+
+
+def _get_embedded_page(page_widget):
+ return page_widget.child.child
+
+
+def _paint_arrow(widget, event, arrow_type):
+ alloc = widget.allocation
+ x = alloc.x + alloc.width / 2 - _ARROW_SIZE / 2
+ y = alloc.y + alloc.height - int(_ARROW_SIZE * .85)
+
+ widget.get_style().paint_arrow(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, widget,
+ None, arrow_type, True, x, y, _ARROW_SIZE, _ARROW_SIZE)
diff --git a/toolitem.py b/toolkit/toolitem.py
index 1f4ee49..e490c22 100644
--- a/toolitem.py
+++ b/toolkit/toolitem.py
@@ -22,6 +22,8 @@ import gobject
from sugar.graphics import style
+from toolkit.combobox import ComboBox
+
class ToolWidget(gtk.ToolItem):
diff --git a/voice.py b/voice.py
index bddca1b..7997354 100644
--- a/voice.py
+++ b/voice.py
@@ -7,12 +7,12 @@
#
# Parts of Speak.activity are based on code from Measure.activity
# Copyright (C) 2007 Arjun Sarwal - arjun@laptop.org
-#
+#
# Speak.activity 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 3 of the License, or
# (at your option) any later version.
-#
+#
# Speak.activity 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
@@ -21,7 +21,7 @@
# You should have received a copy of the GNU General Public License
# along with Speak.activity. If not, see <http://www.gnu.org/licenses/>.
-import re
+import re
import os
from gettext import gettext as _
@@ -30,10 +30,8 @@ logger = logging.getLogger('speak')
import espeak
-# Lets trick gettext into generating entries
-# for the voice names we expect espeak to have
-# If espeak actually has new or different names
-# then they won't get translated, but they
+# Lets trick gettext into generating entries for the voice names we expect espeak to have
+# If espeak actually has new or different names then they won't get translated, but they
# should still show up in the interface.
expectedVoiceNames = [
_("Brazil"),
@@ -74,42 +72,43 @@ expectedVoiceNames = [
_allVoices = {}
_defaultVoice = None
-
class Voice:
def __init__(self, language, name):
self.language = language
self.name = name
friendlyname = name
- friendlyname = friendlyname.replace('-test', '')
- friendlyname = friendlyname.replace('_test', '')
- friendlyname = friendlyname.replace('en-', '')
- friendlyname = friendlyname.replace('english-wisper', 'whisper')
+ friendlyname = friendlyname.replace('-test','')
+ friendlyname = friendlyname.replace('_test','')
+ friendlyname = friendlyname.replace('en-','')
+ friendlyname = friendlyname.replace('english-wisper','whisper')
friendlyname = friendlyname.replace('english-us', 'us')
+
- friendlynameRP = name # friendlyname for RP
+ friendlynameRP = name # friendlyname for RP
friendlynameRP = friendlynameRP.replace('english_rp', 'rp')
friendlynameRP = friendlynameRP.replace('english_wmids', 'wmids')
parts = re.split('[ _-]', friendlyname)
- partsRP = re.split('[ _]', friendlynameRP) # RE for english_RP
+ partsRP = re.split('[ _]', friendlynameRP) #RE for english_RP
self.short_name = _(parts[0].capitalize())
self.friendlyname = ' '.join([self.short_name] + parts[1:])
-
- friendlynameRP1 = None
+
+ friendlynameRP1 = None
if friendlynameRP == 'rp':
-
+
friendlynameRP1 = 'English (Received Pronunciation)'
self.friendlyname = 'English (Received Pronunciation)'
-
+
friendlynameUS = None
if friendlyname == 'us':
friendlynameUS = 'English (USA)'
self.friendlyname = 'English (USA)'
-
+
friendlynameWMIDS = None
if friendlynameRP == 'wmids':
friendlynameWMIDS = 'English (West Midlands)'
- self.friendlyname = 'English (West Midlands)'
+ self.friendlyname = 'English (West Midlands)'
+
def __cmp__(self, other):
return cmp(self.friendlyname, other.friendlyname if other else '')
@@ -122,14 +121,14 @@ def allVoices():
for language, name in espeak.voices():
voice = Voice(language, name)
_allVoices[voice.friendlyname] = voice
-
+
return _allVoices
def by_name(name):
return allVoices().get(name, defaultVoice())
-
+
def defaultVoice():
"""Try to figure out the default voice, from the current locale ($LANG).
Fall back to espeak's voice called Default."""
@@ -141,11 +140,11 @@ def defaultVoice():
voices = allVoices()
- def fit(a, b):
+ def fit(a,b):
"Compare two language ids to see if they are similar."
- as_ = re.split(r'[^a-z]+', a.lower())
- bs = re.split(r'[^a-z]+', b.lower())
- for count in range(0, min(len(as_), len(bs))):
+ as_ = re.split(r'[^a-z]+', a.lower())
+ bs = re.split(r'[^a-z]+', b.lower())
+ for count in range(0, min(len(as_),len(bs))):
if as_[count] != bs[count]:
count -= 1
break
@@ -158,16 +157,14 @@ def defaultVoice():
best = voices[_("Default")]
for voice in voices.values():
voiceMetric = fit(voice.language, lang)
- bestMetric = fit(best.language, lang)
- if lang == 'en_AU.UTF-8':
- if voice.friendlyname == 'English (Received Pronunciation)':
+ bestMetric = fit(best.language, lang)
+ if lang=='en_AU.UTF-8':
+ if voice.friendlyname=='English (Received Pronunciation)':
best = voice
break
if voiceMetric > bestMetric:
best = voice
- print "Best voice for LANG %s seems to be %s %s" % (lang,
- best.language,
- best.friendlyname)
- _defaultVoice = best
+ print "Best voice for LANG %s seems to be %s %s" % (lang, best.language, best.friendlyname)
+ _defaultVoice = best
return best
diff --git a/waveform_mouth.py b/waveform_mouth.py
index b9c4238..71a10ea 100644
--- a/waveform_mouth.py
+++ b/waveform_mouth.py
@@ -7,17 +7,17 @@
#
# Parts of Speak.activity are based on code from Measure.activity
# Copyright (C) 2007 Arjun Sarwal - arjun@laptop.org
-#
+#
# Speak.activity 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 3 of the License, or
# (at your option) any later version.
-#
+#
# Speak.activity 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 Speak.activity. If not, see <http://www.gnu.org/licenses/>.
@@ -25,63 +25,56 @@
from mouth import *
-
class WaveformMouth(Mouth):
def __init__(self, audioSource, fill_color):
Mouth.__init__(self, audioSource, fill_color)
-
+
self.buffer_size = 100
self.peaks = []
- self.stop = False
+ self.stop=False
self.y_mag_bias_multiplier = 1
self.y_mag = 0.7
def expose(self, widget, event):
- """This function is the "expose"
- event handler and does all the drawing."""
+ """This function is the "expose" event handler and does all the drawing."""
bounds = self.get_allocation()
- self.param1 = bounds.height / 65536.0
- self.param2 = bounds.height / 2.0
+ self.param1 = bounds.height/65536.0
+ self.param2 = bounds.height/2.0
#Create context, disable antialiasing
self.context = widget.window.cairo_create()
self.context.set_antialias(cairo.ANTIALIAS_NONE)
- #set a clip region for the expose event.
- #This reduces redrawing work (and time)
- self.context.rectangle(event.area.x,
- event.area.y,
- event.area.width,
- event.area.height)
+ #set a clip region for the expose event. This reduces redrawing work (and time)
+ self.context.rectangle(event.area.x, event.area.y,event.area.width, event.area.height)
self.context.clip()
# background
self.context.set_source_rgba(*self.fill_color.get_rgba())
- self.context.rectangle(0, 0, bounds.width, bounds.height)
+ self.context.rectangle(0,0, bounds.width,bounds.height)
self.context.fill()
# Draw the waveform
- self.context.set_line_width(min(bounds.height / 10.0, 10))
+ self.context.set_line_width(min(bounds.height/10.0, 10))
count = 0
buflen = float(len(self.main_buffers))
for value in self.main_buffers:
- peak = float(self.param1 * value * self.y_mag) +\
- self.y_mag_bias_multiplier * self.param2
+ peak = float(self.param1*value*self.y_mag) + self.y_mag_bias_multiplier * self.param2
if peak >= bounds.height:
peak = bounds.height
if peak <= 0:
peak = 0
-
- x = count / buflen * bounds.width
- self.context.line_to(x, bounds. height - peak)
-
+
+ x = count / buflen * bounds.width
+ self.context.line_to(x,bounds.height - peak)
+
count += 1
- self.context.set_source_rgb(0, 0, 0)
+ self.context.set_source_rgb(0,0,0)
self.context.stroke()
return True