From c9450cfdf43b638b2d65b5aad7362a259cae3e57 Mon Sep 17 00:00:00 2001 From: Joshua Minor Date: Mon, 11 Feb 2008 08:59:40 +0000 Subject: Eyes now track the text cursor Eyes now face ahead when speaking Added text history popup + arrow key navigation Fixed mouth corners so they match Text doesn't disappear when you press Enter --- diff --git a/Speak.activity/activity.py b/Speak.activity/activity.py index fbe96ad..bac8f89 100755 --- a/Speak.activity/activity.py +++ b/Speak.activity/activity.py @@ -68,9 +68,12 @@ class SpeakActivity(activity.Activity): #self.proc = None # make a box to type into - self.entry = gtk.Entry() + self.entrycombo = gtk.combo_box_entry_new_text() + self.entrycombo.connect("changed", self._combo_changed_cb) + self.entry = self.entrycombo.child self.entry.set_editable(True) - self.entry.connect('activate', self.entry_activate_cb) + 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) @@ -86,7 +89,7 @@ class SpeakActivity(activity.Activity): box = gtk.VBox(homogeneous=False) box.pack_start(self.eyebox, expand=False) box.pack_start(self.mouthbox) - box.pack_start(self.entry, expand=False) + box.pack_start(self.entrycombo, expand=False) self.set_canvas(box) box.show_all() @@ -112,18 +115,62 @@ class SpeakActivity(activity.Activity): # make the text box active right away self.entry.grab_focus() - - # start polling for audio - #gobject.timeout_add(100, self._timeout_cb) + self.entry.connect("move-cursor", self._cursor_moved_cb) + self.entry.connect("changed", self._cursor_moved_cb) + + # try to catch all mouse-moved events so the eyes will track wherever you go + # this doesn't work for some reason I don't understand + # it gets mouse motion over lots of stuff, but not sliders or comboboxes + # import time + # self.window.set_events(self.window.get_events() | gtk.gdk.POINTER_MOTION_MASK) + # def event_filter(event, user_data=None): + # map(lambda w: w.queue_draw(), self.eyes) + # print time.asctime(), time.time(), event.get_coords(), event.get_root_coords() + # return gtk.gdk.FILTER_CONTINUE + # self.window.add_filter(event_filter) + # map(lambda c: c.forall(lambda w: w.add_events(gtk.gdk.POINTER_MOTION_MASK)), self.window.get_children()) + + # start polling for mouse movement + # self.mouseX = None + # self.mouseY = None + # def poll_mouse(): + # display = gtk.gdk.display_get_default() + # screen, mouseX, mouseY, modifiers = display.get_pointer() + # if self.mouseX != mouseX or self.mouseY != mouseY: + # self.mouseX = mouseX + # self.mouseY = mouseY + # map(lambda w: w.queue_draw(), self.eyes) + # return True + # gobject.timeout_add(100, poll_mouse) + + # start with the eyes straight ahead + map(lambda e: e.look_ahead(), self.eyes) + # say hello to the user self.active = True presenceService = presenceservice.get_instance() xoOwner = presenceService.get_owner() - self.say("Hello %s, my name is XO. Type something." % xoOwner.props.nick) + self.say("Hello %s. Type something." % xoOwner.props.nick) + + 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 + map(lambda e, x=x, y=y: e.look_at(x,y), self.eyes) + + def get_mouse(self): + display = gtk.gdk.display_get_default() + screen, mouseX, mouseY, modifiers = display.get_pointer() + return mouseX, mouseY def _mouse_moved_cb(self, widget, event): - map(lambda w: w.queue_draw(), self.eyes) + # make the eyes track the motion of the mouse cursor + x,y = self.get_mouse() + map(lambda e, x=x, y=y: e.look_at(x,y), self.eyes) def _mouse_clicked_cb(self, widget, event): pass @@ -186,18 +233,11 @@ class SpeakActivity(activity.Activity): def rate_adjusted_cb(self, get, data=None): self.say("rate adjusted") - def make_face_bar(self): facebar = gtk.Toolbar() self.numeyesadj = None - # button = ToolButton('change-voice') - # button.set_tooltip("Change Voice") - # button.connect('clicked', self.change_voice_cb) - # facebar.insert(button, -1) - # button.show() - combo = ComboBox() combo.connect('changed', self.mouth_changed_cb) combo.append_item(mouth.Mouth, "Simple") @@ -264,23 +304,53 @@ class SpeakActivity(activity.Activity): # this SegFaults: self.say(self.eye_shape_combo.get_active_text()) self.say("eyes changed") - - def _timeout_cb(self): - # make the mouth update with the latest waveform - # ideally we would only do this when the audio is actually playing - if self.mouth: - self.mouth.queue_draw(); - return True - - def entry_activate_cb(self, entry): + + 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 = gtk.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 index20: + 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 say(self, something): if self.audio is None or not self.active: diff --git a/Speak.activity/activity/activity.info b/Speak.activity/activity/activity.info index 6029cec..9fffe88 100644 --- a/Speak.activity/activity/activity.info +++ b/Speak.activity/activity/activity.info @@ -3,5 +3,5 @@ name = Speak service_name = vu.lux.olpc.Speak class = activity.SpeakActivity icon = activity-speak -activity_version = 3 +activity_version = 4 show_launcher = yes diff --git a/Speak.activity/eye.py b/Speak.activity/eye.py index 72d9170..689a6d0 100644 --- a/Speak.activity/eye.py +++ b/Speak.activity/eye.py @@ -30,9 +30,10 @@ import math class Eye(gtk.DrawingArea): def __init__(self): gtk.DrawingArea.__init__(self) - self.connect("expose_event",self.expose) + self.connect("expose_event", self.expose) self.frame = 0 self.blink = False + self.x, self.y = 0,0 # listen for clicks self.add_events(gtk.gdk.BUTTON_PRESS_MASK) @@ -59,22 +60,40 @@ class Eye(gtk.DrawingArea): self.blink = False self.queue_draw() - def get_mouse(self): - display = gtk.gdk.display_get_default() - screen, mouseX, mouseY, modifiers = display.get_pointer() - return mouseX, mouseY + def look_at(self, x, y): + self.x = x + self.y = y + self.queue_draw() + + def look_ahead(self): + self.x = None + self.y = None + self.queue_draw() + + def pupil_position(self): + bounds = self.get_allocation() + abs_x, abs_y = self.translate_coordinates(self.get_toplevel(), 0, 0) + if self.x is not None and self.y is not None: + dir_x, dir_y = self.x - abs_x, self.y - abs_y + else: + # look ahead, but not *directly* in the middle + if bounds.x+bounds.width/2 < self.parent.get_allocation().width/2: + dir_x = bounds.width * 0.6 + else: + dir_x = bounds.width * 0.4 + dir_y = bounds.height * 0.6 + pupilX = max(min(dir_x, bounds.width), 0) + pupilY = max(min(dir_y, bounds.height), 0) + return pupilX, pupilY def expose(self, widget, event): self.frame += 1 bounds = self.get_allocation() - mouseX, mouseY = self.get_mouse() - eyeSize = min(bounds.width, bounds.height) outlineWidth = eyeSize/20.0 pupilSize = eyeSize/10.0 - pupilX = max(min(mouseX - bounds.x, bounds.width), 0) - pupilY = max(min(mouseY - bounds.y, bounds.height), 0) + pupilX, pupilY = self.pupil_position() dX = pupilX - bounds.width/2. dY = pupilY - bounds.height/2. distance = math.sqrt(dX*dX + dY*dY) diff --git a/Speak.activity/glasses.py b/Speak.activity/glasses.py index afc68ef..61965a6 100644 --- a/Speak.activity/glasses.py +++ b/Speak.activity/glasses.py @@ -29,13 +29,10 @@ class Glasses(Eye): def expose(self, widget, event): bounds = self.get_allocation() - mouseX, mouseY = self.get_mouse() - eyeSize = min(bounds.width, bounds.height) outlineWidth = eyeSize/20.0 pupilSize = eyeSize/10.0 - pupilX = max(min(mouseX - bounds.x, bounds.width), 0) - pupilY = max(min(mouseY - bounds.y, bounds.height), 0) + pupilX, pupilY = self.pupil_position() dX = pupilX - bounds.width/2. dY = pupilY - bounds.height/2. distance = math.sqrt(dX*dX + dY*dY) diff --git a/Speak.activity/mouth.py b/Speak.activity/mouth.py index 453675d..7fb7197 100644 --- a/Speak.activity/mouth.py +++ b/Speak.activity/mouth.py @@ -88,6 +88,7 @@ class Mouth(gtk.DrawingArea): self.context.curve_to(Tx,Ty, Tx,Ty, Rx,Ry) self.context.curve_to(Bx,By, Bx,By, Lx,Ly) self.context.set_source_rgb(0,0,0) + self.context.close_path() self.context.stroke() return True diff --git a/dist/speak.xo b/dist/speak.xo index 9ee0ec3..a240395 100644 --- a/dist/speak.xo +++ b/dist/speak.xo Binary files differ -- cgit v0.9.1