Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/activity.py
diff options
context:
space:
mode:
authorIvaDobreva <iva95dobreva@gmail.com>2012-12-30 20:15:52 (GMT)
committer IvaDobreva <iva95dobreva@gmail.com>2012-12-30 20:15:52 (GMT)
commite0f866f2b21482ea71d8700bcf3923d029bca1b3 (patch)
tree46ffb79943fde8325a5e74ce1317abbaaa258919 /activity.py
gtk3
Diffstat (limited to 'activity.py')
-rw-r--r--activity.py484
1 files changed, 484 insertions, 0 deletions
diff --git a/activity.py b/activity.py
new file mode 100644
index 0000000..33213ba
--- /dev/null
+++ b/activity.py
@@ -0,0 +1,484 @@
+# Speak.activity
+# A simple front end to the espeak text-to-speech engine on the XO laptop
+# http://wiki.laptop.org/go/Speak
+#
+# Copyright (C) 2008 Joshua Minor
+# This file is part of Speak.activity
+#
+# 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/>.
+
+
+from sugar3.activity import activity
+from sugar3.presence import presenceservice
+import logging
+from gi.repository import Gtk
+from gi.repository import GObject
+from gi.repository import Pango
+import json
+from gettext import gettext as _
+
+from sugar3.graphics.toolbutton import ToolButton
+from sugar3.graphics.toggletoolbutton import ToggleToolButton
+from sugar3.graphics.radiotoolbutton import RadioToolButton
+
+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 *
+
+import eye
+import glasses
+import mouth
+import fft_mouth
+import waveform_mouth
+import voice
+import face
+import brain
+import chat
+import espeak
+from messenger import Messenger, SERVICE
+
+logger = logging.getLogger('speak')
+
+MODE_TYPE = 1
+MODE_BOT = 2
+MODE_CHAT = 3
+
+
+class SpeakActivity(SharedActivity):
+ def __init__(self, handle):
+ self.notebook = Gtk.Notebook()
+
+ SharedActivity.__init__(self, self.notebook, SERVICE, handle)
+
+ self._mode = MODE_TYPE
+ self.numeyesadj = None
+
+ # make an audio device for playing back and rendering audio
+ self.connect("notify::active", self._activeCb)
+
+ # make a box to type into
+ self.entrycombo = Gtk.combo_box_entry_new_text()
+ self.entrycombo.connect("changed", self._combo_changed_cb)
+ self.entry = self.entrycombo.get_child()
+ self.entry.set_editable(True)
+ self.entry.connect('activate', self._entry_activate_cb)
+ self.entry.connect("key-press-event", self._entry_key_press_cb)
+ self.input_font = Pango.FontDescription(str='sans bold 24')
+ self.entry.modify_font(self.input_font)
+
+ self.face = face.View()
+ self.face.show()
+
+ # layout the screen
+ box = Gtk.Vbox(False, 0)
+ box.set_size_request(Gdk.Screen.width(), Gdk.Screen.height())
+ box.add(self.face)
+ box.add(self.entrycombo)
+
+ self.add_events(Gdk.EventMask.POINTER_MOTION_HINT_MASK
+ | Gdk.EventMask.POINTER_MOTION_MASK)
+ self.connect("motion_notify_event", self._mouse_moved_cb)
+
+ box.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
+ box.connect("button_press_event", self._mouse_clicked_cb)
+
+ # desktop
+ self.notebook.show()
+ self.notebook.props.show_border=False
+ self.notebook.props.show_tabs=False
+
+ box.show_all()
+ self.notebook.append_page(box)
+
+ self.chat = chat.View()
+ self.chat.show_all()
+ self.notebook.append_page(self.chat)
+
+ # make the text box active right away
+ self.entry.grab_focus()
+
+ self.entry.connect("move-cursor", self._cursor_moved_cb)
+ self.entry.connect("changed", self._cursor_moved_cb)
+
+ # toolbar
+ toolbox = ToolbarBox()
+
+ toolbox.toolbar.insert(ActivityToolbarButton(self), -1)
+
+ self.voices = ComboBox()
+ for name in sorted(voice.allVoices().keys()):
+ vn = voice.allVoices()[name]
+ n = name [ : 26 ] + ".."
+ self.voices.append_item(vn, n)
+
+ self.voices.select(voice.defaultVoice())
+ all_voices = self.voices.get_model()
+ brain_voices = brain.get_voices()
+
+ mode_type = RadioToolButton(
+ named_icon='mode-type',
+ tooltip=_('Type something to hear it'))
+ mode_type.connect('toggled', self.__toggled_mode_type_cb, all_voices)
+ toolbox.toolbar.insert(mode_type, -1)
+
+ mode_robot = RadioToolButton(
+ named_icon='mode-robot',
+ group=mode_type,
+ tooltip=_('Ask robot any question'))
+ mode_robot.connect('toggled', self.__toggled_mode_robot_cb,
+ brain_voices)
+ toolbox.toolbar.insert(mode_robot, -1)
+
+ mode_chat = RadioToolButton(
+ named_icon='mode-chat',
+ group=mode_type,
+ tooltip=_('Voice chat'))
+ mode_chat.connect('toggled', self.__toggled_mode_chat_cb, all_voices)
+ toolbox.toolbar.insert(mode_chat, -1)
+
+ voices_toolitem = ToolWidget(widget=self.voices)
+ toolbox.toolbar.insert(voices_toolitem, -1)
+
+ voice_button = ToolbarButton(
+ page=self.make_voice_bar(),
+ label=_('Voice'),
+ icon_name='voice')
+ toolbox.toolbar.insert(voice_button, -1)
+
+ face_button = ToolbarButton(
+ page=self.make_face_bar(),
+ label=_('Face'),
+ icon_name='face')
+ toolbox.toolbar.insert(face_button, -1)
+
+ separator = Gtk.SeparatorToolItem()
+ separator.set_draw(False)
+ separator.set_expand(True)
+ toolbox.toolbar.insert(separator, -1)
+
+ toolbox.toolbar.insert(StopButton(self), -1)
+
+ toolbox.show_all()
+ self.toolbar_box = toolbox
+
+ 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.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)
+ self.eye_shape_combo.connect('changed', self.eyes_changed_cb, False)
+ self.eyes_changed_cb(None, True)
+
+ self.face.look_ahead()
+
+ # say hello to the user
+ presenceService = presenceservice.get_instance()
+ xoOwner = presenceService.get_owner()
+ self.face.say_notification(_("Hello %s. Please Type something.") \
+ % xoOwner.props.nick)
+
+ def resume_instance(self, file_path):
+ cfg = json.loads(file(file_path, 'r').read())
+
+ status = self.face.status = face.Status().deserialize(cfg['status'])
+ self.voices.select(status.voice)
+ self.pitchadj.value = self.face.status.pitch
+ self.rateadj.value = self.face.status.rate
+ self.mouth_shape_combo.select(status.mouth)
+ self.eye_shape_combo.select(status.eyes[0])
+ self.numeyesadj.value = len(status.eyes)
+
+ self.entry.props.text = cfg['text'].encode('utf-8', 'ignore')
+ for i in cfg['history']:
+ self.entrycombo.append_text(i.encode('utf-8', 'ignore'))
+
+ self.new_instance()
+
+ def save_instance(self, file_path):
+ cfg = {'status': self.face.status.serialize(),
+ 'text': unicode(self.entry.props.text, 'utf-8', 'ignore'),
+ 'history': [unicode(i[0], 'utf-8', 'ignore') \
+ for i in self.entrycombo.get_model()],
+ }
+ file(file_path, 'w').write(json.dumps(cfg))
+
+ def share_instance(self, connection, is_initiator):
+ self.chat.messenger = Messenger(connection, is_initiator, self.chat)
+
+ def _cursor_moved_cb(self, entry, *ignored):
+ # make the eyes track the motion of the text cursor
+ index = entry.props.cursor_position
+ layout = entry.get_layout()
+ pos = layout.get_cursor_pos(index)
+ x = pos[0][0] / Pango.SCALE - entry.props.scroll_offset
+ y = entry.get_allocation().y
+ self.face.look_at(pos=(x, y))
+
+ def get_mouse(self):
+ display = Gdk.Display.get_default()
+ screen, mouseX, mouseY, modifiers = display.get_pointer()
+ return mouseX, mouseY
+
+ def _mouse_moved_cb(self, widget, event):
+ # make the eyes track the motion of the mouse cursor
+ self.face.look_at()
+ self.chat.look_at()
+
+ def _mouse_clicked_cb(self, widget, event):
+ pass
+
+ def make_voice_bar(self):
+ voicebar = Gtk.Toolbar()
+
+ self.pitchadj = Gtk.Adjustment(self.face.status.pitch, 0,
+ espeak.PITCH_MAX, 1, espeak.PITCH_MAX/10, 0)
+ pitchbar = Gtk.HScale(self.pitchadj)
+ pitchbar.set_draw_value(False)
+ # pitchbar.set_inverted(True)
+ pitchbar.set_update_policy(Gtk.UPDATE_DISCONTINUOUS)
+ pitchbar.set_size_request(240, 15)
+
+ pitchbar_toolitem = ToolWidget(
+ widget=pitchbar,
+ 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)
+ ratebar = Gtk.HScale(self.rateadj)
+ ratebar.set_draw_value(False)
+ # ratebar.set_inverted(True)
+ ratebar.set_update_policy(Gtk.UPDATE_DISCONTINUOUS)
+ ratebar.set_size_request(240, 15)
+
+ ratebar_toolitem = ToolWidget(
+ widget=ratebar,
+ label_text=_('Rate:'))
+ voicebar.insert(ratebar_toolitem, -1)
+
+ voicebar.show_all()
+ return voicebar
+
+ def pitch_adjusted_cb(self, get, data=None):
+ self.face.status.pitch = get.value
+ self.face.say_notification(_("pitch adjusted"))
+
+ def rate_adjusted_cb(self, get, data=None):
+ self.face.status.rate = get.value
+ self.face.say_notification(_("rate adjusted"))
+
+ def make_face_bar(self):
+ facebar = Gtk.Toolbar()
+
+ 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(fft_mouth.FFTMouth, _("Frequency"))
+ self.mouth_shape_combo.set_active(0)
+
+ mouth_shape_toolitem = ToolWidget(
+ widget=self.mouth_shape_combo,
+ label_text=_('Mouth:'))
+ facebar.insert(mouth_shape_toolitem, -1)
+
+ self.eye_shape_combo = ComboBox()
+ self.eye_shape_combo.append_item(eye.Eye, _("Round"))
+ self.eye_shape_combo.append_item(glasses.Glasses, _("Glasses"))
+ self.eye_shape_combo.set_active(0)
+
+ eye_shape_toolitem = ToolWidget(
+ widget=self.eye_shape_combo,
+ label_text=_('Eyes:'))
+ facebar.insert(eye_shape_toolitem, -1)
+
+ self.numeyesadj = Gtk.Adjustment(2, 1, 5, 1, 1, 0)
+ numeyesbar = Gtk.HScale(self.numeyesadj)
+ numeyesbar.set_draw_value(False)
+ numeyesbar.set_update_policy(Gtk.UPDATE_DISCONTINUOUS)
+ numeyesbar.set_size_request(240, 15)
+
+ numeyesbar_toolitem = ToolWidget(
+ widget=numeyesbar,
+ label_text=_('Eyes number:'))
+ facebar.insert(numeyesbar_toolitem, -1)
+
+ facebar.show_all()
+ return facebar
+
+ def mouth_changed_cb(self, combo, quiet):
+ self.face.status.mouth = combo.props.value
+ self._update_face()
+
+ # this SegFaults: self.face.say(combo.get_active_text())
+ if not quiet:
+ self.face.say_notification(_("mouth changed"))
+
+ def eyes_changed_cb(self, ignored, quiet):
+ if self.numeyesadj is None:
+ return
+
+ self.face.status.eyes = [self.eye_shape_combo.props.value] \
+ * int(self.numeyesadj.value)
+ self._update_face()
+
+ # this SegFaults: self.face.say(self.eye_shape_combo.get_active_text())
+ if not quiet:
+ self.face.say_notification(_("eyes changed"))
+
+ def _update_face(self):
+ self.face.update()
+ self.chat.update(self.face.status)
+
+ def _combo_changed_cb(self, combo):
+ # when a new item is chosen, make sure the text is selected
+ if not self.entry.is_focus():
+ self.entry.grab_focus()
+ self.entry.select_region(0, -1)
+
+ def _entry_key_press_cb(self, combo, event):
+ # make the up/down arrows navigate through our history
+ keyname = Gdk.keyval_name(event.keyval)
+ if keyname == "Up":
+ index = self.entrycombo.get_active()
+ if index>0:
+ index-=1
+ self.entrycombo.set_active(index)
+ 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
+ self.entrycombo.set_active(index)
+ self.entry.select_region(0, -1)
+ return True
+ return False
+
+ def _entry_activate_cb(self, entry):
+ # the user pressed Return, say the text and clear it out
+ text = entry.props.text
+ if text:
+ self.face.look_ahead()
+
+ # speak the text
+ if self._mode == MODE_BOT:
+ self.face.say(
+ brain.respond(self.voices.props.value, text))
+ else:
+ self.face.say(text)
+
+ # 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:
+ self.entrycombo.append_text(text)
+ # don't let the history get too big
+ while len(history)>20:
+ self.entrycombo.remove_text(0)
+ # select the new item
+ self.entrycombo.set_active(len(history)-1)
+ # select the whole text
+ entry.select_region(0, -1)
+
+ def _activeCb(self, widget, pspec):
+ # only generate sound when this activity is active
+ if not self.props.active:
+ self.face.shut_up()
+ self.chat.shut_up()
+
+ def _set_voice(self, new_voice):
+ try:
+ self.voices.handler_block_by_func(self.__changed_voices_cb)
+ self.voices.select(new_voice)
+ self.face.status.voice = new_voice
+ finally:
+ self.voices.handler_unblock_by_func(self.__changed_voices_cb)
+
+ def __toggled_mode_type_cb(self, button, voices_model):
+ if not button.props.active:
+ return
+
+ self._mode = MODE_TYPE
+ self.chat.shut_up()
+ self.face.shut_up()
+ self.notebook.set_current_page(0)
+
+ old_voice = self.voices.props.value
+ self.voices.set_model(voices_model)
+ self._set_voice(old_voice)
+
+ def __toggled_mode_robot_cb(self, button, voices_model):
+ if not button.props.active:
+ return
+
+ self._mode = MODE_BOT
+ self.chat.shut_up()
+ self.face.shut_up()
+ self.notebook.set_current_page(0)
+
+ old_voice = self.voices.props.value
+ self.voices.set_model(voices_model)
+
+ new_voice = [i[0] for i in voices_model
+ if i[0].short_name == old_voice.short_name]
+ if not new_voice:
+ new_voice = brain.get_default_voice()
+ sorry = _("Sorry, I can't speak %(old_voice)s, " \
+ "let's talk %(new_voice)s instead.") % {
+ 'old_voice': old_voice.friendlyname,
+ 'new_voice': new_voice.friendlyname,
+ }
+ else:
+ new_voice = new_voice[0]
+ sorry = None
+
+ self._set_voice(new_voice)
+
+ if not brain.load(self, self.voices.props.value, sorry):
+ if sorry:
+ self.face.say_notification(sorry)
+
+ def __toggled_mode_chat_cb(self, button, voices_model):
+ if not button.props.active:
+ return
+
+ is_first_session = not self.chat.me.get_mapped()
+
+ self._mode = MODE_CHAT
+ self.face.shut_up()
+ self.notebook.set_current_page(1)
+
+ old_voice = self.voices.props.value
+ self.voices.set_model(voices_model)
+ self._set_voice(old_voice)
+
+ if is_first_session:
+ self.chat.me.say_notification(
+ _("You are in off-line mode, share and invite someone."))
+
+ def __changed_voices_cb(self, combo):
+ voice = combo.props.value
+ self.face.set_voice(voice)
+ if self._mode == MODE_BOT:
+ brain.load(self, voice)
+
+
+# activate Gtk threads when this module loads
+Gdk.threads_init()