From 2ba5c5514955cdd87308b2f67d3d329944e714c3 Mon Sep 17 00:00:00 2001 From: Rafael Ortiz Date: Sun, 22 Jul 2012 20:53:48 +0000 Subject: Merge branch 'master' of git.sugarlabs.org:speak/mainline --- 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 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 . +# You should have received a copy of the GNU General Public License +# along with HablarConSara.activity. If not, see . 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 = '' % self._color.get_html() +\ - text + '' - 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 . @@ -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 . -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 . @@ -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 . @@ -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 . @@ -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 \n" "Language-Team: LANGUAGE \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 \n" "Language-Team: LANGUAGE \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 \n" +"PO-Revision-Date: 2012-06-25 21:48+0200\n" +"Last-Translator: mattthias \n" "Language-Team: LANGUAGE \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 \n" "Language-Team: LANGUAGE \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 \n" "Language-Team: LANGUAGE \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 \n" "Language-Team: LANGUAGE \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 \n" "Language-Team: LANGUAGE \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 \n" +"PO-Revision-Date: 2012-06-24 02:20+0200\n" +"Last-Translator: ing.arturo.lara \n" "Language-Team: LANGUAGE \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 \n" "Language-Team: LANGUAGE \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. \n" +"PO-Revision-Date: 2012-06-25 01:45+0200\n" +"Last-Translator: Luis \n" "Language-Team: LANGUAGE \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 \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 \n" +"POT-Creation-Date: 2012-06-07 00:36-0400\n" +"PO-Revision-Date: 2012-06-24 01:47+0200\n" +"Last-Translator: lite \n" "Language-Team: LANGUAGE \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 . 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 = '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 = '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 +# 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] = """ + + + + +""" + +IMAGE[1] = """ + + + + +""" + +IMAGE[2] = """ + + + + +""" + +IMAGE[3] = """ + + + + +""" + +IMAGE[4] = """ + + + + +""" + +IMAGE[5] = """ + + + + + +""" + +IMAGE[6] = """ + + + + +""" + +IMAGE[7] = """ + + + + +""" 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 . -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 . @@ -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 -- cgit v0.9.1