diff options
author | root <root@ghunt-desktop.(none)> | 2010-06-09 14:57:19 (GMT) |
---|---|---|
committer | root <root@ghunt-desktop.(none)> | 2010-06-09 14:57:19 (GMT) |
commit | 5f4860099ae80a996e2e4dee85c72c11e170fc8e (patch) | |
tree | d2eeb7f67e553ddb50d802994f045af5aca846e1 /examples | |
parent | 3449b69734b57b5a5c625d43e90fde241a14ca6d (diff) |
fixes to save-as
Diffstat (limited to 'examples')
76 files changed, 6273 insertions, 0 deletions
diff --git a/examples/JS_examples b/examples/JS_examples deleted file mode 160000 -Subproject c8833e114e9962b2214c08ed308e6bdfd784a2e diff --git a/examples/JS_examples/.gitignore b/examples/JS_examples/.gitignore new file mode 100644 index 0000000..ca67c95 --- /dev/null +++ b/examples/JS_examples/.gitignore @@ -0,0 +1,7 @@ +*.pyc +*.e4p +*.zip +.eric4project/ +.ropeproject/ +dist + diff --git a/examples/JS_examples/Add_Refinements/MANIFEST b/examples/JS_examples/Add_Refinements/MANIFEST new file mode 100644 index 0000000..e724c0c --- /dev/null +++ b/examples/JS_examples/Add_Refinements/MANIFEST @@ -0,0 +1,6 @@ +toolbar.py +setup.py +ReadEtextsActivity2.py +activity/read-etexts.svg +activity/activity.info +po/ReadEtextsII.pot diff --git a/examples/JS_examples/Add_Refinements/ReadEtextsActivity2.py b/examples/JS_examples/Add_Refinements/ReadEtextsActivity2.py new file mode 100644 index 0000000..d11be21 --- /dev/null +++ b/examples/JS_examples/Add_Refinements/ReadEtextsActivity2.py @@ -0,0 +1,328 @@ +# +# ReadEtextsActivity2.py A version of ReadEtextsActivity with better +# toolbars and other refinements. +# Copyright (C) 2010 James D. Simmons +# +# 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 os +import zipfile +import gtk +import pango +from sugar.activity import activity +from sugar.graphics import style +from toolbar import ReadToolbar, ViewToolbar +from gettext import gettext as _ + +page=0 +PAGE_SIZE = 45 +TOOLBAR_READ = 2 + +class ReadEtextsActivity(activity.Activity): + def __init__(self, handle): + "The entry point to the Activity" + global page + activity.Activity.__init__(self, handle) + + toolbox = activity.ActivityToolbox(self) + activity_toolbar = toolbox.get_activity_toolbar() + activity_toolbar.keep.props.visible = False + activity_toolbar.share.props.visible = False + + self.edit_toolbar = activity.EditToolbar() + self.edit_toolbar.undo.props.visible = False + self.edit_toolbar.redo.props.visible = False + self.edit_toolbar.separator.props.visible = False + self.edit_toolbar.copy.set_sensitive(False) + self.edit_toolbar.copy.connect('clicked', self.edit_toolbar_copy_cb) + self.edit_toolbar.paste.props.visible = False + toolbox.add_toolbar(_('Edit'), self.edit_toolbar) + self.edit_toolbar.show() + + self.read_toolbar = ReadToolbar() + toolbox.add_toolbar(_('Read'), self.read_toolbar) + self.read_toolbar.back.connect('clicked', self.go_back_cb) + self.read_toolbar.forward.connect('clicked', self.go_forward_cb) + self.read_toolbar.num_page_entry.connect('activate', + self.num_page_entry_activate_cb) + self.read_toolbar.show() + + self.view_toolbar = ViewToolbar() + toolbox.add_toolbar(_('View'), self.view_toolbar) + self.view_toolbar.connect('go-fullscreen', + self.view_toolbar_go_fullscreen_cb) + self.view_toolbar.zoom_in.connect('clicked', self.zoom_in_cb) + self.view_toolbar.zoom_out.connect('clicked', self.zoom_out_cb) + self.view_toolbar.show() + + self.set_toolbox(toolbox) + toolbox.show() + self.scrolled_window = gtk.ScrolledWindow() + self.scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self.scrolled_window.props.shadow_type = gtk.SHADOW_NONE + + self.textview = gtk.TextView() + self.textview.set_editable(False) + self.textview.set_cursor_visible(False) + self.textview.set_left_margin(50) + self.textview.connect("key_press_event", self.keypress_cb) + + self.scrolled_window.add(self.textview) + self.set_canvas(self.scrolled_window) + self.textview.show() + self.scrolled_window.show() + page = 0 + self.clipboard = gtk.Clipboard(display=gtk.gdk.display_get_default(), \ + selection="CLIPBOARD") + self.textview.grab_focus() + self.font_desc = pango.FontDescription("sans %d" % style.zoom(10)) + self.textview.modify_font(self.font_desc) + + buffer = self.textview.get_buffer() + self.markset_id = buffer.connect("mark-set", self.mark_set_cb) + self.toolbox.set_current_toolbar(TOOLBAR_READ) + + def keypress_cb(self, widget, event): + "Respond when the user presses one of the arrow keys" + keyname = gtk.gdk.keyval_name(event.keyval) + print keyname + if keyname == 'plus': + self.font_increase() + return True + if keyname == 'minus': + self.font_decrease() + return True + if keyname == 'Page_Up' : + self.page_previous() + return True + if keyname == 'Page_Down': + self.page_next() + return True + if keyname == 'Up' or keyname == 'KP_Up' \ + or keyname == 'KP_Left': + self.scroll_up() + return True + if keyname == 'Down' or keyname == 'KP_Down' \ + or keyname == 'KP_Right': + self.scroll_down() + return True + return False + + def num_page_entry_activate_cb(self, entry): + global page + if entry.props.text: + new_page = int(entry.props.text) - 1 + else: + new_page = 0 + + if new_page >= self.read_toolbar.total_pages: + new_page = self.read_toolbar.total_pages - 1 + elif new_page < 0: + new_page = 0 + + self.read_toolbar.current_page = new_page + self.read_toolbar.set_current_page(new_page) + self.show_page(new_page) + entry.props.text = str(new_page + 1) + self.read_toolbar.update_nav_buttons() + page = new_page + + def go_back_cb(self, button): + self.page_previous() + + def go_forward_cb(self, button): + self.page_next() + + def page_previous(self): + global page + page=page-1 + if page < 0: page=0 + self.read_toolbar.set_current_page(page) + self.show_page(page) + v_adjustment = self.scrolled_window.get_vadjustment() + v_adjustment.value = v_adjustment.upper - v_adjustment.page_size + + def page_next(self): + global page + page=page+1 + if page >= len(self.page_index): page=0 + self.read_toolbar.set_current_page(page) + self.show_page(page) + v_adjustment = self.scrolled_window.get_vadjustment() + v_adjustment.value = v_adjustment.lower + + def zoom_in_cb(self, button): + self.font_increase() + + def zoom_out_cb(self, button): + self.font_decrease() + + def font_decrease(self): + font_size = self.font_desc.get_size() / 1024 + font_size = font_size - 1 + if font_size < 1: + font_size = 1 + self.font_desc.set_size(font_size * 1024) + self.textview.modify_font(self.font_desc) + + def font_increase(self): + font_size = self.font_desc.get_size() / 1024 + font_size = font_size + 1 + self.font_desc.set_size(font_size * 1024) + self.textview.modify_font(self.font_desc) + + def mark_set_cb(self, textbuffer, iter, textmark): + + if textbuffer.get_has_selection(): + begin, end = textbuffer.get_selection_bounds() + self.edit_toolbar.copy.set_sensitive(True) + else: + self.edit_toolbar.copy.set_sensitive(False) + + def edit_toolbar_copy_cb(self, button): + textbuffer = self.textview.get_buffer() + begin, end = textbuffer.get_selection_bounds() + copy_text = textbuffer.get_text(begin, end) + self.clipboard.set_text(copy_text) + + def view_toolbar_go_fullscreen_cb(self, view_toolbar): + self.fullscreen() + + def scroll_down(self): + v_adjustment = self.scrolled_window.get_vadjustment() + if v_adjustment.value == v_adjustment.upper - \ + v_adjustment.page_size: + self.page_next() + return + if v_adjustment.value < v_adjustment.upper - \ + v_adjustment.page_size: + new_value = v_adjustment.value + v_adjustment.step_increment + if new_value > v_adjustment.upper - v_adjustment.page_size: + new_value = v_adjustment.upper - v_adjustment.page_size + v_adjustment.value = new_value + + def scroll_up(self): + v_adjustment = self.scrolled_window.get_vadjustment() + if v_adjustment.value == v_adjustment.lower: + self.page_previous() + return + if v_adjustment.value > v_adjustment.lower: + new_value = v_adjustment.value - \ + v_adjustment.step_increment + if new_value < v_adjustment.lower: + new_value = v_adjustment.lower + v_adjustment.value = new_value + + def show_page(self, page_number): + global PAGE_SIZE, current_word + position = self.page_index[page_number] + self.etext_file.seek(position) + linecount = 0 + label_text = '\n\n\n' + textbuffer = self.textview.get_buffer() + while linecount < PAGE_SIZE: + line = self.etext_file.readline() + label_text = label_text + unicode(line, 'iso-8859-1') + linecount = linecount + 1 + label_text = label_text + '\n\n\n' + textbuffer.set_text(label_text) + self.textview.set_buffer(textbuffer) + + def save_extracted_file(self, zipfile, filename): + "Extract the file to a temp directory for viewing" + filebytes = zipfile.read(filename) + outfn = self.make_new_filename(filename) + if (outfn == ''): + return False + f = open(os.path.join(self.get_activity_root(), 'tmp', outfn), 'w') + try: + f.write(filebytes) + finally: + f.close() + + def get_saved_page_number(self): + global page + title = self.metadata.get('title', '') + if title == '' or not title[len(title)- 1].isdigit(): + page = 0 + else: + i = len(title) - 1 + newPage = '' + while (title[i].isdigit() and i > 0): + newPage = title[i] + newPage + i = i - 1 + if title[i] == 'P': + page = int(newPage) - 1 + else: + # not a page number; maybe a volume number. + page = 0 + + def save_page_number(self): + global page + title = self.metadata.get('title', '') + if title == '' or not title[len(title)- 1].isdigit(): + title = title + ' P' + str(page + 1) + else: + i = len(title) - 1 + while (title[i].isdigit() and i > 0): + i = i - 1 + if title[i] == 'P': + title = title[0:i] + 'P' + str(page + 1) + else: + title = title + ' P' + str(page + 1) + self.metadata['title'] = title + + def read_file(self, filename): + "Read the Etext file" + global PAGE_SIZE, page + + if zipfile.is_zipfile(filename): + self.zf = zipfile.ZipFile(filename, 'r') + self.book_files = self.zf.namelist() + self.save_extracted_file(self.zf, self.book_files[0]) + currentFileName = os.path.join(self.get_activity_root(), \ + 'tmp', self.book_files[0]) + else: + currentFileName = filename + + self.etext_file = open(currentFileName,"r") + self.page_index = [ 0 ] + pagecount = 0 + linecount = 0 + while self.etext_file: + line = self.etext_file.readline() + if not line: + break + linecount = linecount + 1 + if linecount >= PAGE_SIZE: + position = self.etext_file.tell() + self.page_index.append(position) + linecount = 0 + pagecount = pagecount + 1 + if filename.endswith(".zip"): + os.remove(currentFileName) + self.get_saved_page_number() + self.show_page(page) + self.read_toolbar.set_total_pages(pagecount + 1) + self.read_toolbar.set_current_page(page) + + def make_new_filename(self, filename): + partition_tuple = filename.rpartition('/') + return partition_tuple[2] + + def write_file(self, filename): + "Save meta data for the file." + self.metadata['activity'] = self.get_bundle_id() + self.save_page_number() diff --git a/examples/JS_examples/Add_Refinements/activity/activity.info b/examples/JS_examples/Add_Refinements/activity/activity.info new file mode 100644 index 0000000..738f496 --- /dev/null +++ b/examples/JS_examples/Add_Refinements/activity/activity.info @@ -0,0 +1,9 @@ +[Activity] +name = ReadEtexts II +service_name = net.flossmanuals.ReadETextsActivity2 +icon = read-etexts +exec = sugar-activity ReadEtextsActivity2.ReadEtextsActivity +show_launcher = no +mime_types = text/plain;application/zip +activity_version = 1 +license = GPLv2+ diff --git a/examples/JS_examples/Add_Refinements/activity/activity.info~ b/examples/JS_examples/Add_Refinements/activity/activity.info~ new file mode 100644 index 0000000..137333f --- /dev/null +++ b/examples/JS_examples/Add_Refinements/activity/activity.info~ @@ -0,0 +1,9 @@ +[Activity] +name = ReadEtexts II +service_name = net.flossmanuals.ReadETexts +icon = read-etexts +exec = sugar-activity ReadEtextsActivity4.ReadEtextsActivity +show_launcher = no +mime_types = text/plain;application/zip +activity_version = 1 +license = GPLv2+ diff --git a/examples/JS_examples/Add_Refinements/activity/read-etexts.svg b/examples/JS_examples/Add_Refinements/activity/read-etexts.svg new file mode 100644 index 0000000..5682ec8 --- /dev/null +++ b/examples/JS_examples/Add_Refinements/activity/read-etexts.svg @@ -0,0 +1,71 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#000000"> + <!ENTITY fill_color "#FFFFFF"> +]><svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="48px" + height="48px" + id="svg116" + sodipodi:version="0.32" + inkscape:version="0.46+devel" + sodipodi:docname="New document 4" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs118"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="48 : 24 : 1" + inkscape:persp3d-origin="24 : 16 : 1" + id="perspective124" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="7" + inkscape:cx="24" + inkscape:cy="24" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:grid-bbox="true" + inkscape:document-units="px" + inkscape:window-width="1024" + inkscape:window-height="698" + inkscape:window-x="0" + inkscape:window-y="25" /> + <metadata + id="metadata121"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:label="Layer 1" + inkscape:groupmode="layer"> + <rect + style="fill:&fill_color;;stroke:&stroke_color;;stroke-opacity:1" + id="rect904" + width="36.142857" + height="32.142857" + x="4.1428571" + y="7.1428571" /> + </g> +</svg> diff --git a/examples/JS_examples/Add_Refinements/po/ReadEtextsII.pot b/examples/JS_examples/Add_Refinements/po/ReadEtextsII.pot new file mode 100644 index 0000000..0df8967 --- /dev/null +++ b/examples/JS_examples/Add_Refinements/po/ReadEtextsII.pot @@ -0,0 +1,53 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-03-05 09:47-0600\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: activity/activity.info:2 +msgid "ReadEtexts II" +msgstr "" + +#: toolbar.py:36 +msgid "Back" +msgstr "" + +#: toolbar.py:42 +msgid "Forward" +msgstr "" + +#: toolbar.py:117 +msgid "Zoom out" +msgstr "" + +#: toolbar.py:122 +msgid "Zoom in" +msgstr "" + +#: toolbar.py:132 +msgid "Fullscreen" +msgstr "" + +#: ReadEtextsActivity2.py:54 +msgid "Edit" +msgstr "" + +#: ReadEtextsActivity2.py:58 +msgid "Read" +msgstr "" + +#: ReadEtextsActivity2.py:66 +msgid "View" +msgstr "" diff --git a/examples/JS_examples/Add_Refinements/setup.py b/examples/JS_examples/Add_Refinements/setup.py new file mode 100755 index 0000000..ebc201d --- /dev/null +++ b/examples/JS_examples/Add_Refinements/setup.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +# setup.py + +# Copyright (C) 2006, Red Hat, Inc. +# +# 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 + +from sugar.activity import bundlebuilder + +bundlebuilder.start() diff --git a/examples/JS_examples/Add_Refinements/toolbar.py b/examples/JS_examples/Add_Refinements/toolbar.py new file mode 100644 index 0000000..564602c --- /dev/null +++ b/examples/JS_examples/Add_Refinements/toolbar.py @@ -0,0 +1,138 @@ +# toolbar.,py The toolbars used by ReadEtextsActivity. +# +# Copyright (C) 2010, James Simmons. +# Adapted from code Copyright (C) Red Hat Inc. +# +# 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 + +from gettext import gettext as _ +import re + +import pango +import gobject +import gtk + +from sugar.graphics.toolbutton import ToolButton + +class ReadToolbar(gtk.Toolbar): + __gtype_name__ = 'ReadToolbar' + + def __init__(self): + gtk.Toolbar.__init__(self) + + self.back = ToolButton('go-previous') + self.back.set_tooltip(_('Back')) + self.back.props.sensitive = False + self.insert(self.back, -1) + self.back.show() + + self.forward = ToolButton('go-next') + self.forward.set_tooltip(_('Forward')) + self.forward.props.sensitive = False + self.insert(self.forward, -1) + self.forward.show() + + num_page_item = gtk.ToolItem() + + self.num_page_entry = gtk.Entry() + self.num_page_entry.set_text('0') + self.num_page_entry.set_alignment(1) + self.num_page_entry.connect('insert-text', + self.num_page_entry_insert_text_cb) + + self.num_page_entry.set_width_chars(4) + + num_page_item.add(self.num_page_entry) + self.num_page_entry.show() + + self.insert(num_page_item, -1) + num_page_item.show() + + total_page_item = gtk.ToolItem() + + self.total_page_label = gtk.Label() + + label_attributes = pango.AttrList() + label_attributes.insert(pango.AttrSize(14000, 0, -1)) + label_attributes.insert(pango.AttrForeground(65535, 65535, 65535, 0, -1)) + self.total_page_label.set_attributes(label_attributes) + + self.total_page_label.set_text(' / 0') + total_page_item.add(self.total_page_label) + self.total_page_label.show() + + self.insert(total_page_item, -1) + total_page_item.show() + + def num_page_entry_insert_text_cb(self, entry, text, length, position): + if not re.match('[0-9]', text): + entry.emit_stop_by_name('insert-text') + return True + return False + + def update_nav_buttons(self): + current_page = self.current_page + self.back.props.sensitive = current_page > 0 + self.forward.props.sensitive = \ + current_page < self.total_pages - 1 + + self.num_page_entry.props.text = str(current_page + 1) + self.total_page_label.props.label = \ + ' / ' + str(self.total_pages) + + def set_total_pages(self, pages): + self.total_pages = pages + + def set_current_page(self, page): + self.current_page = page + self.update_nav_buttons() + +class ViewToolbar(gtk.Toolbar): + __gtype_name__ = 'ViewToolbar' + + __gsignals__ = { + 'needs-update-size': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])), + 'go-fullscreen': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])) + } + + def __init__(self): + gtk.Toolbar.__init__(self) + self.zoom_out = ToolButton('zoom-out') + self.zoom_out.set_tooltip(_('Zoom out')) + self.insert(self.zoom_out, -1) + self.zoom_out.show() + + self.zoom_in = ToolButton('zoom-in') + self.zoom_in.set_tooltip(_('Zoom in')) + self.insert(self.zoom_in, -1) + self.zoom_in.show() + + spacer = gtk.SeparatorToolItem() + spacer.props.draw = False + self.insert(spacer, -1) + spacer.show() + + self.fullscreen = ToolButton('view-fullscreen') + self.fullscreen.set_tooltip(_('Fullscreen')) + self.fullscreen.connect('clicked', self.fullscreen_cb) + self.insert(self.fullscreen, -1) + self.fullscreen.show() + + def fullscreen_cb(self, button): + self.emit('go-fullscreen') diff --git a/examples/JS_examples/Adding_TTS/ReadEtextsTTS.py b/examples/JS_examples/Adding_TTS/ReadEtextsTTS.py new file mode 100755 index 0000000..28e3cfd --- /dev/null +++ b/examples/JS_examples/Adding_TTS/ReadEtextsTTS.py @@ -0,0 +1,271 @@ +#! /usr/bin/env python +# +# ReadEtextsTTS.py A standalone ebook reader program that +# demonstrates text to speech with word highlighting. +# Copyright (C) 2010 James D. Simmons +# +# 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 sys +import os +import zipfile +import pygtk +import gtk +import getopt +import pango +import gobject +import time +import speech + +speech_supported = True + +try: + import gst + gst.element_factory_make('espeak') + print 'speech supported!' +except Exception, e: + speech_supported = False + print 'speech not supported!' + +page=0 +PAGE_SIZE = 45 + +class ReadEtextsActivity(): + def __init__(self): + "The entry point to the Activity" + speech.highlight_cb = self.highlight_next_word + # print speech.voices() + + def highlight_next_word(self, word_count): + if word_count < len(self.word_tuples): + word_tuple = self.word_tuples[word_count] + textbuffer = self.textview.get_buffer() + tag = textbuffer.create_tag() + tag.set_property('weight', pango.WEIGHT_BOLD) + tag.set_property( 'foreground', "white") + tag.set_property( 'background', "black") + iterStart = textbuffer.get_iter_at_offset(word_tuple[0]) + iterEnd = textbuffer.get_iter_at_offset(word_tuple[1]) + bounds = textbuffer.get_bounds() + textbuffer.remove_all_tags(bounds[0], bounds[1]) + textbuffer.apply_tag(tag, iterStart, iterEnd) + v_adjustment = self.scrolled_window.get_vadjustment() + max = v_adjustment.upper - v_adjustment.page_size + max = max * word_count + max = max / len(self.word_tuples) + v_adjustment.value = max + return True + + def keypress_cb(self, widget, event): + "Respond when the user presses one of the arrow keys" + global done + global speech_supported + keyname = gtk.gdk.keyval_name(event.keyval) + if keyname == 'KP_End' and speech_supported: + if speech.is_paused() or speech.is_stopped(): + speech.play(self.words_on_page) + else: + speech.pause() + return True + if keyname == 'plus': + self.font_increase() + return True + if keyname == 'minus': + self.font_decrease() + return True + if speech_supported and speech.is_stopped() == False and speech.is_paused == False: + # If speech is in progress, ignore other keys. + return True + if keyname == '7': + speech.pitch_down() + speech.say('Pitch Adjusted') + return True + if keyname == '8': + speech.pitch_up() + speech.say('Pitch Adjusted') + return True + if keyname == '9': + speech.rate_down() + speech.say('Rate Adjusted') + return True + if keyname == '0': + speech.rate_up() + speech.say('Rate Adjusted') + return True + if keyname == 'KP_Right': + self.page_next() + return True + if keyname == 'Page_Up' or keyname == 'KP_Up': + self.page_previous() + return True + if keyname == 'KP_Left': + self.page_previous() + return True + if keyname == 'Page_Down' or keyname == 'KP_Down': + self.page_next() + return True + if keyname == 'Up': + self.scroll_up() + return True + if keyname == 'Down': + self.scroll_down() + return True + return False + + def page_previous(self): + global page + page=page-1 + if page < 0: page=0 + self.show_page(page) + v_adjustment = self.scrolled_window.get_vadjustment() + v_adjustment.value = v_adjustment.upper - v_adjustment.page_size + + def page_next(self): + global page + page=page+1 + if page >= len(self.page_index): page=0 + self.show_page(page) + v_adjustment = self.scrolled_window.get_vadjustment() + v_adjustment.value = v_adjustment.lower + + def font_decrease(self): + font_size = self.font_desc.get_size() / 1024 + font_size = font_size - 1 + if font_size < 1: + font_size = 1 + self.font_desc.set_size(font_size * 1024) + self.textview.modify_font(self.font_desc) + + def font_increase(self): + font_size = self.font_desc.get_size() / 1024 + font_size = font_size + 1 + self.font_desc.set_size(font_size * 1024) + self.textview.modify_font(self.font_desc) + + def scroll_down(self): + v_adjustment = self.scrolled_window.get_vadjustment() + if v_adjustment.value == v_adjustment.upper - v_adjustment.page_size: + self.page_next() + return + if v_adjustment.value < v_adjustment.upper - v_adjustment.page_size: + new_value = v_adjustment.value + v_adjustment.step_increment + if new_value > v_adjustment.upper - v_adjustment.page_size: + new_value = v_adjustment.upper - v_adjustment.page_size + v_adjustment.value = new_value + + def scroll_up(self): + v_adjustment = self.scrolled_window.get_vadjustment() + if v_adjustment.value == v_adjustment.lower: + self.page_previous() + return + if v_adjustment.value > v_adjustment.lower: + new_value = v_adjustment.value - v_adjustment.step_increment + if new_value < v_adjustment.lower: + new_value = v_adjustment.lower + v_adjustment.value = new_value + + def show_page(self, page_number): + global PAGE_SIZE, current_word + position = self.page_index[page_number] + self.etext_file.seek(position) + linecount = 0 + label_text = '' + textbuffer = self.textview.get_buffer() + while linecount < PAGE_SIZE: + line = self.etext_file.readline() + label_text = label_text + unicode(line, 'iso-8859-1') + linecount = linecount + 1 + textbuffer.set_text(label_text) + self.textview.set_buffer(textbuffer) + self.word_tuples = speech.prepare_highlighting(label_text) + self.words_on_page = speech.add_word_marks(self.word_tuples) + + def save_extracted_file(self, zipfile, filename): + "Extract the file to a temp directory for viewing" + filebytes = zipfile.read(filename) + f = open("/tmp/" + filename, 'w') + try: + f.write(filebytes) + finally: + f.close() + + def read_file(self, filename): + "Read the Etext file" + global PAGE_SIZE + + if zipfile.is_zipfile(filename): + self.zf = zipfile.ZipFile(filename, 'r') + self.book_files = self.zf.namelist() + self.save_extracted_file(self.zf, self.book_files[0]) + currentFileName = "/tmp/" + self.book_files[0] + else: + currentFileName = filename + + self.etext_file = open(currentFileName,"r") + self.page_index = [ 0 ] + linecount = 0 + while self.etext_file: + line = self.etext_file.readline() + if not line: + break + linecount = linecount + 1 + if linecount >= PAGE_SIZE: + position = self.etext_file.tell() + self.page_index.append(position) + linecount = 0 + if filename.endswith(".zip"): + os.remove(currentFileName) + + def delete_cb(self, widget, event, data=None): + speech.stop() + return False + + def destroy_cb(self, widget, data=None): + speech.stop() + gtk.main_quit() + + def main(self, file_path): + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window.connect("delete_event", self.delete_cb) + self.window.connect("destroy", self.destroy_cb) + self.window.set_title("Read Etexts Activity") + self.window.set_size_request(800, 600) + self.window.set_border_width(0) + self.read_file(file_path) + self.scrolled_window = gtk.ScrolledWindow(hadjustment=None, vadjustment=None) + self.textview = gtk.TextView() + self.textview.set_editable(False) + self.textview.set_left_margin(50) + self.textview.set_cursor_visible(False) + self.textview.connect("key_press_event", self.keypress_cb) + self.font_desc = pango.FontDescription("sans 12") + self.textview.modify_font(self.font_desc) + self.show_page(0) + self.scrolled_window.add(self.textview) + self.window.add(self.scrolled_window) + self.textview.show() + self.scrolled_window.show() + self.window.show() + gtk.main() + +if __name__ == "__main__": + try: + opts, args = getopt.getopt(sys.argv[1:], "") + ReadEtextsActivity().main(args[0]) + except getopt.error, msg: + print msg + print "This program has no options" + sys.exit(2) diff --git a/examples/JS_examples/Adding_TTS/espeak.py b/examples/JS_examples/Adding_TTS/espeak.py new file mode 100755 index 0000000..fbb1b22 --- /dev/null +++ b/examples/JS_examples/Adding_TTS/espeak.py @@ -0,0 +1,63 @@ +#! /usr/bin/env python +# +# espeak.py +# Use espeak, part of Make Your Own Sugar Activities Book Examples. +# Copyright (C) 2010 James D. Simmons +# +# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +import re +import subprocess + +PITCH_MAX = 99 +RATE_MAX = 99 +PITCH_DEFAULT = PITCH_MAX/2 +RATE_DEFAULT = RATE_MAX/3 + +def speak(text, rate=RATE_DEFAULT, pitch=PITCH_DEFAULT, voice="default"): + + # espeak uses 80 to 370 + rate = 80 + (370-80) * int(rate) / 100 + + subprocess.call(["espeak", "-p", str(pitch), + "-s", str(rate), "-v", voice, text], + stdout=subprocess.PIPE) + +def voices(): + out = [] + result = subprocess.Popen(["espeak", "--voices"], stdout=subprocess.PIPE) \ + .communicate()[0] + + for line in result.split('\n'): + m = re.match(r'\s*\d+\s+([\w-]+)\s+([MF])\s+([\w_-]+)\s+(.+)', line) + if not m: + continue + language, gender, name, stuff = m.groups() + if stuff.startswith('mb/') or \ + name in ('en-rhotic','english_rp','english_wmids'): + # these voices don't produce sound + continue + out.append((language, name)) + + return out + +def main(): + print voices() + speak("I'm afraid I can't do that, Dave.") + speak("Your mother was a hamster, and your father smelled of elderberries!", 30, 60, "fr") + +if __name__ == "__main__": + main() diff --git a/examples/JS_examples/Adding_TTS/gst_choir_example.py b/examples/JS_examples/Adding_TTS/gst_choir_example.py new file mode 100755 index 0000000..9ea653f --- /dev/null +++ b/examples/JS_examples/Adding_TTS/gst_choir_example.py @@ -0,0 +1,51 @@ +# +# <one line to give the program's name and a brief idea of what it does.> +# Copyright (C) <YEAR> <NAME> +# +# 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 +import gst +import random +from gettext import gettext as _ + +def gstmessage_cb(bus, message, pipe): + if message.type in (gst.MESSAGE_EOS, gst.MESSAGE_ERROR): + pipe.set_state(gst.STATE_NULL) + +def make_pipe(): + pipeline = 'espeak name=src ! autoaudiosink' + pipe = gst.parse_launch(pipeline) + + src = pipe.get_by_name('src') + src.props.text = _('Hello, World!') + src.props.pitch = random.randint(-100, 100) + src.props.rate = random.randint(-100, 100) + + voices = src.props.voices + voice = voices[random.randint(0, len(voices)-1)] + src.props.voice = voice[0] + + bus = pipe.get_bus() + bus.add_signal_watch() + bus.connect('message', gstmessage_cb, pipe) + + pipe.set_state(gst.STATE_PLAYING) + +for i in range(10): + make_pipe() + +gtk.main() diff --git a/examples/JS_examples/Adding_TTS/gst_simple_example.py b/examples/JS_examples/Adding_TTS/gst_simple_example.py new file mode 100755 index 0000000..6ae2707 --- /dev/null +++ b/examples/JS_examples/Adding_TTS/gst_simple_example.py @@ -0,0 +1,36 @@ +# +# <one line to give the program's name and a brief idea of what it does.> +# Copyright (C) <YEAR> <NAME> +# +# 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 +import gst + +def gstmessage_cb(bus, message, pipe): + if message.type in (gst.MESSAGE_EOS, gst.MESSAGE_ERROR): + pipe.set_state(gst.STATE_NULL) + +pipeline = 'espeak text="Hello, World!" ! autoaudiosink' +pipe = gst.parse_launch(pipeline) + +bus = pipe.get_bus() +bus.add_signal_watch() +bus.connect('message', gstmessage_cb, pipe) + +pipe.set_state(gst.STATE_PLAYING) + +gtk.main() diff --git a/examples/JS_examples/Adding_TTS/gst_simple_tts.py b/examples/JS_examples/Adding_TTS/gst_simple_tts.py new file mode 100755 index 0000000..34dcccf --- /dev/null +++ b/examples/JS_examples/Adding_TTS/gst_simple_tts.py @@ -0,0 +1,93 @@ +# gst_simple_tts.py +# Copyright (C) 2010 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 +import gst +import pango + +window = gtk.Window() +window.connect('destroy', + lambda sender: gtk.main_quit()) + +workspace = gtk.VBox() +window.add(workspace) + +# text widget + +scrolled = gtk.ScrolledWindow() +workspace.pack_start(scrolled) + +text = gtk.TextView() +text.set_left_margin(50) +text.set_right_margin(50) +text.set_wrap_mode(gtk.WRAP_WORD) +scrolled.add(text) + +buffer = text.props.buffer +buffer.props.text = file("testtts.txt").read() + +tag = buffer.create_tag() +tag.props.weight = pango.WEIGHT_BOLD + +# play controls + +toolbar = gtk.HBox() +workspace.pack_end(toolbar, False) + +play = gtk.Button('Play/Resume') +play.connect('clicked', + lambda sender: pipe.set_state(gst.STATE_PLAYING)) +toolbar.add(play) + +pause = gtk.Button('Pause') +pause.connect('clicked', + lambda sender: pipe.set_state(gst.STATE_PAUSED)) +toolbar.add(pause) + +stop = gtk.Button('Stop') +stop.connect('clicked', + lambda sender: pipe.set_state(gst.STATE_NULL)) +toolbar.add(stop) + +# gst code + +pipe = gst.parse_launch('espeak name=src ! autoaudiosink') + +src = pipe.get_by_name('src') +src.props.text = buffer.props.text +src.props.track = 1 # track for words + +def tts_cb(bus, message): + if message.structure.get_name() != 'espeak-word': + return + + offset = message.structure['offset'] + len = message.structure['len'] + + buffer.remove_tag(tag, buffer.get_start_iter(), buffer.get_end_iter()) + start = buffer.get_iter_at_offset(offset) + end = buffer.get_iter_at_offset(offset + len) + buffer.apply_tag(tag, start, end) + +bus = pipe.get_bus() +bus.add_signal_watch() +bus.connect('message::element', tts_cb) + +# gtk start + +window.show_all() +gtk.main() diff --git a/examples/JS_examples/Adding_TTS/gst_track_marks.py b/examples/JS_examples/Adding_TTS/gst_track_marks.py new file mode 100755 index 0000000..5eecb78 --- /dev/null +++ b/examples/JS_examples/Adding_TTS/gst_track_marks.py @@ -0,0 +1,54 @@ +# +# gst_track_marks.py +# Sample code for using the gstreamer espeak plugin. + +# Copyright (C) 2010 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 +import gst + +text = '<mark name="mark to Hello"/>Hello, <mark name="mark for World"/>World!' + +def gstmessage_cb(bus, message, pipe): + if message.type in (gst.MESSAGE_EOS, gst.MESSAGE_ERROR): + pipe.set_state(gst.STATE_NULL) + elif message.type == gst.MESSAGE_ELEMENT and \ + message.structure.get_name() == 'espeak-mark': + offset = message.structure['offset'] + mark = message.structure['mark'] + print '%d:%s' % (offset, mark) + +pipe = gst.Pipeline('pipeline') + +src = gst.element_factory_make('espeak', 'src') +src.props.text = text +src.props.track = 2 +src.props.gap = 100 +pipe.add(src) + +sink = gst.element_factory_make('autoaudiosink', 'sink') +pipe.add(sink) +src.link(sink) + +bus = pipe.get_bus() +bus.add_signal_watch() +bus.connect('message', gstmessage_cb, pipe) + +pipe.set_state(gst.STATE_PLAYING) + +gtk.main() diff --git a/examples/JS_examples/Adding_TTS/gst_track_words.py b/examples/JS_examples/Adding_TTS/gst_track_words.py new file mode 100755 index 0000000..fe61c8a --- /dev/null +++ b/examples/JS_examples/Adding_TTS/gst_track_words.py @@ -0,0 +1,50 @@ +# gst_track_words.py +# Sample code for using the gstreamer espeak plugin. +# Copyright (C) 2010 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 +import gst + +text = file(__file__, 'r').read() + +def gstmessage_cb(bus, message, pipe): + if message.type in (gst.MESSAGE_EOS, gst.MESSAGE_ERROR): + pipe.set_state(gst.STATE_NULL) + elif message.type == gst.MESSAGE_ELEMENT and \ + message.structure.get_name() == 'espeak-word': + offset = message.structure['offset'] + len = message.structure['len'] + print text[offset:offset+len] + +pipe = gst.Pipeline('pipeline') + +src = gst.element_factory_make('espeak', 'src') +src.props.text = text +src.props.track = 1 +pipe.add(src) + +sink = gst.element_factory_make('autoaudiosink', 'sink') +pipe.add(sink) +src.link(sink) + +bus = pipe.get_bus() +bus.add_signal_watch() +bus.connect('message', gstmessage_cb, pipe) + +pipe.set_state(gst.STATE_PLAYING) + +gtk.main() diff --git a/examples/JS_examples/Adding_TTS/speech.py b/examples/JS_examples/Adding_TTS/speech.py new file mode 100644 index 0000000..cdab7d2 --- /dev/null +++ b/examples/JS_examples/Adding_TTS/speech.py @@ -0,0 +1,136 @@ +# speech.py + +# Copyright (C) 2010 Aleksey Lim and James D. Simmons +# +# 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 gst + +voice = 'default' +pitch = 0 + +rate = -20 +highlight_cb = None + +def _create_pipe(): + pipeline = 'espeak name=source ! autoaudiosink' + pipe = gst.parse_launch(pipeline) + + def stop_cb(bus, message): + pipe.set_state(gst.STATE_NULL) + + def mark_cb(bus, message): + if message.structure.get_name() == 'espeak-mark': + mark = message.structure['mark'] + highlight_cb(int(mark)) + + bus = pipe.get_bus() + bus.add_signal_watch() + bus.connect('message::eos', stop_cb) + bus.connect('message::error', stop_cb) + bus.connect('message::element', mark_cb) + + return (pipe.get_by_name('source'), pipe) + +def _speech(source, pipe, words): + source.props.pitch = pitch + source.props.rate = rate + source.props.voice = voice + source.props.text = words; + pipe.set_state(gst.STATE_PLAYING) + +info_source, info_pipe = _create_pipe() +play_source, play_pipe = _create_pipe() + +# track for marks +play_source.props.track = 2 + +def voices(): + return info_source.props.voices + +def say(words): + _speech(info_source, info_pipe, words) + print words + +def play(words): + _speech(play_source, play_pipe, words) + +def is_stopped(): + for i in play_pipe.get_state(): + if isinstance(i, gst.State) and i == gst.STATE_NULL: + return True + return False + +def stop(): + play_pipe.set_state(gst.STATE_NULL) + +def is_paused(): + for i in play_pipe.get_state(): + if isinstance(i, gst.State) and i == gst.STATE_PAUSED: + return True + return False + +def pause(): + play_pipe.set_state(gst.STATE_PAUSED) + +def rate_up(): + global rate + rate = min(99, rate + 10) + +def rate_down(): + global rate + rate = max(-99, rate - 10) + +def pitch_up(): + global pitch + pitch = min(99, pitch + 10) + +def pitch_down(): + global pitch + pitch = max(-99, pitch - 10) + +def prepare_highlighting(label_text): + i = 0 + j = 0 + word_begin = 0 + word_end = 0 + current_word = 0 + word_tuples = [] + omitted = [' ', '\n', u'\r', '_', '[', '{', ']', '}', '|', '<',\ + '>', '*', '+', '/', '\\' ] + omitted_chars = set(omitted) + while i < len(label_text): + if label_text[i] not in omitted_chars: + word_begin = i + j = i + while j < len(label_text) and label_text[j] not in omitted_chars: + j = j + 1 + word_end = j + i = j + word_t = (word_begin, word_end, label_text[word_begin: word_end].strip()) + if word_t[2] != u'\r': + word_tuples.append(word_t) + i = i + 1 + return word_tuples + +def add_word_marks(word_tuples): + "Adds a mark between each word of text." + i = 0 + marked_up_text = '<speak> ' + while i < len(word_tuples): + word_t = word_tuples[i] + marked_up_text = marked_up_text + '<mark name="' + str(i) + '"/>' + word_t[2] + i = i + 1 + return marked_up_text + '</speak>' diff --git a/examples/JS_examples/Adding_TTS/testtts.txt b/examples/JS_examples/Adding_TTS/testtts.txt new file mode 100644 index 0000000..4057792 --- /dev/null +++ b/examples/JS_examples/Adding_TTS/testtts.txt @@ -0,0 +1,21 @@ +Jane Austen? Why I go so far as to say that any library is a good library that does not contain a volume by Jane +Austen. Even if it contains no other book. + +- quoted in Remembered Yesterdays, Robert Underwood Johnson + +To me his prose is unreadable -- like Jane Austin's [sic]. No there is a difference. I could read his prose on salary, but +not Jane's. Jane is entirely impossible. It seems a great pity that they allowed her to die a natural death. + +- Letter to W. D. Howells, 18 January 1909 + +Jane Austen's books, too, are absent from this library. Just that one omission alone would make a +fairly good library out of a library that hadn't a book in it. + +- Following the Equator + +I haven't any right to criticise books, and I don't do it except when I hate them. I often want to criticise +Jane Austen, but her books madden me so that I can't conceal my frenzy from the reader; and therefore I have +to stop every time I begin. Everytime I read 'Pride and Prejudice' I want to dig her up and beat her over +the skull with her own shin-bone. + +- Letter to Joseph Twichell, 13 September 1898 diff --git a/examples/JS_examples/Inherit_From_sugar.activity.Activity/MANIFEST b/examples/JS_examples/Inherit_From_sugar.activity.Activity/MANIFEST new file mode 100644 index 0000000..8cba1c5 --- /dev/null +++ b/examples/JS_examples/Inherit_From_sugar.activity.Activity/MANIFEST @@ -0,0 +1,7 @@ + +setup.py +ReadEtextsActivity.py + +activity/read-etexts.svg +activity/activity.info +po/ReadEtextsI.pot diff --git a/examples/JS_examples/Inherit_From_sugar.activity.Activity/ReadEtextsActivity.py b/examples/JS_examples/Inherit_From_sugar.activity.Activity/ReadEtextsActivity.py new file mode 100644 index 0000000..9a9372a --- /dev/null +++ b/examples/JS_examples/Inherit_From_sugar.activity.Activity/ReadEtextsActivity.py @@ -0,0 +1,199 @@ +# +# ReadEtextsActivity.py A very minimal ebook reading Activity for Sugar. +# Copyright (C) 2010 James D. Simmons +# +# 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 os +import zipfile +import gtk +import pango +from sugar.activity import activity +from sugar.graphics import style + +page=0 +PAGE_SIZE = 45 + +class ReadEtextsActivity(activity.Activity): + def __init__(self, handle): + "The entry point to the Activity" + global page + activity.Activity.__init__(self, handle) + + toolbox = activity.ActivityToolbox(self) + activity_toolbar = toolbox.get_activity_toolbar() + activity_toolbar.keep.props.visible = False + activity_toolbar.share.props.visible = False + self.set_toolbox(toolbox) + + toolbox.show() + self.scrolled_window = gtk.ScrolledWindow() + self.scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self.scrolled_window.props.shadow_type = gtk.SHADOW_NONE + + self.textview = gtk.TextView() + self.textview.set_editable(False) + self.textview.set_cursor_visible(False) + self.textview.set_left_margin(50) + self.textview.connect("key_press_event", self.keypress_cb) + + self.scrolled_window.add(self.textview) + self.set_canvas(self.scrolled_window) + self.textview.show() + self.scrolled_window.show() + page = 0 + self.textview.grab_focus() + self.font_desc = pango.FontDescription("sans %d" % style.zoom(10)) + self.textview.modify_font(self.font_desc) + + def keypress_cb(self, widget, event): + "Respond when the user presses one of the arrow keys" + keyname = gtk.gdk.keyval_name(event.keyval) + print keyname + if keyname == 'plus': + self.font_increase() + return True + if keyname == 'minus': + self.font_decrease() + return True + if keyname == 'Page_Up' : + self.page_previous() + return True + if keyname == 'Page_Down': + self.page_next() + return True + if keyname == 'Up' or keyname == 'KP_Up' \ + or keyname == 'KP_Left': + self.scroll_up() + return True + if keyname == 'Down' or keyname == 'KP_Down' \ + or keyname == 'KP_Right': + self.scroll_down() + return True + return False + + def page_previous(self): + global page + page=page-1 + if page < 0: page=0 + self.show_page(page) + v_adjustment = self.scrolled_window.get_vadjustment() + v_adjustment.value = v_adjustment.upper - v_adjustment.page_size + + def page_next(self): + global page + page=page+1 + if page >= len(self.page_index): page=0 + self.show_page(page) + v_adjustment = self.scrolled_window.get_vadjustment() + v_adjustment.value = v_adjustment.lower + + def font_decrease(self): + font_size = self.font_desc.get_size() / 1024 + font_size = font_size - 1 + if font_size < 1: + font_size = 1 + self.font_desc.set_size(font_size * 1024) + self.textview.modify_font(self.font_desc) + + def font_increase(self): + font_size = self.font_desc.get_size() / 1024 + font_size = font_size + 1 + self.font_desc.set_size(font_size * 1024) + self.textview.modify_font(self.font_desc) + + def scroll_down(self): + v_adjustment = self.scrolled_window.get_vadjustment() + if v_adjustment.value == v_adjustment.upper - \ + v_adjustment.page_size: + self.page_next() + return + if v_adjustment.value < v_adjustment.upper - v_adjustment.page_size: + new_value = v_adjustment.value + v_adjustment.step_increment + if new_value > v_adjustment.upper - v_adjustment.page_size: + new_value = v_adjustment.upper - v_adjustment.page_size + v_adjustment.value = new_value + + def scroll_up(self): + v_adjustment = self.scrolled_window.get_vadjustment() + if v_adjustment.value == v_adjustment.lower: + self.page_previous() + return + if v_adjustment.value > v_adjustment.lower: + new_value = v_adjustment.value - \ + v_adjustment.step_increment + if new_value < v_adjustment.lower: + new_value = v_adjustment.lower + v_adjustment.value = new_value + + def show_page(self, page_number): + global PAGE_SIZE, current_word + position = self.page_index[page_number] + self.etext_file.seek(position) + linecount = 0 + label_text = '\n\n\n' + textbuffer = self.textview.get_buffer() + while linecount < PAGE_SIZE: + line = self.etext_file.readline() + label_text = label_text + unicode(line, 'iso-8859-1') + linecount = linecount + 1 + label_text = label_text + '\n\n\n' + textbuffer.set_text(label_text) + self.textview.set_buffer(textbuffer) + + def save_extracted_file(self, zipfile, filename): + "Extract the file to a temp directory for viewing" + filebytes = zipfile.read(filename) + outfn = self.make_new_filename(filename) + if (outfn == ''): + return False + f = open(os.path.join(self.get_activity_root(), 'tmp', outfn), 'w') + try: + f.write(filebytes) + finally: + f.close() + + def read_file(self, filename): + "Read the Etext file" + global PAGE_SIZE + + if zipfile.is_zipfile(filename): + self.zf = zipfile.ZipFile(filename, 'r') + self.book_files = self.zf.namelist() + self.save_extracted_file(self.zf, self.book_files[0]) + currentFileName = os.path.join(self.get_activity_root(), 'tmp', self.book_files[0]) + else: + currentFileName = filename + + self.etext_file = open(currentFileName,"r") + self.page_index = [ 0 ] + linecount = 0 + while self.etext_file: + line = self.etext_file.readline() + if not line: + break + linecount = linecount + 1 + if linecount >= PAGE_SIZE: + position = self.etext_file.tell() + self.page_index.append(position) + linecount = 0 + if filename.endswith(".zip"): + os.remove(currentFileName) + self.show_page(0) + + def make_new_filename(self, filename): + partition_tuple = filename.rpartition('/') + return partition_tuple[2] diff --git a/examples/JS_examples/Inherit_From_sugar.activity.Activity/activity/activity.info b/examples/JS_examples/Inherit_From_sugar.activity.Activity/activity/activity.info new file mode 100644 index 0000000..e1cdf73 --- /dev/null +++ b/examples/JS_examples/Inherit_From_sugar.activity.Activity/activity/activity.info @@ -0,0 +1,9 @@ +[Activity] +name = ReadEtexts I +service_name = net.flossmanuals.ReadETextsActivity +icon = read-etexts +exec = sugar-activity ReadEtextsActivity.ReadEtextsActivity +show_launcher = no +mime_types = text/plain;application/zip +activity_version = 1 +license = GPLv2+ diff --git a/examples/JS_examples/Inherit_From_sugar.activity.Activity/activity/activity.info~ b/examples/JS_examples/Inherit_From_sugar.activity.Activity/activity/activity.info~ new file mode 100644 index 0000000..b696c1e --- /dev/null +++ b/examples/JS_examples/Inherit_From_sugar.activity.Activity/activity/activity.info~ @@ -0,0 +1,9 @@ +[Activity] +name = ReadEtexts II +service_name = net.flossmanuals.ReadETextsActivity +icon = read-etexts +exec = sugar-activity ReadEtextsActivity.ReadEtextsActivity +show_launcher = no +mime_types = text/plain;application/zip +activity_version = 1 +license = GPLv2+ diff --git a/examples/JS_examples/Inherit_From_sugar.activity.Activity/activity/read-etexts.svg b/examples/JS_examples/Inherit_From_sugar.activity.Activity/activity/read-etexts.svg new file mode 100644 index 0000000..5682ec8 --- /dev/null +++ b/examples/JS_examples/Inherit_From_sugar.activity.Activity/activity/read-etexts.svg @@ -0,0 +1,71 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#000000"> + <!ENTITY fill_color "#FFFFFF"> +]><svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="48px" + height="48px" + id="svg116" + sodipodi:version="0.32" + inkscape:version="0.46+devel" + sodipodi:docname="New document 4" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs118"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="48 : 24 : 1" + inkscape:persp3d-origin="24 : 16 : 1" + id="perspective124" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="7" + inkscape:cx="24" + inkscape:cy="24" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:grid-bbox="true" + inkscape:document-units="px" + inkscape:window-width="1024" + inkscape:window-height="698" + inkscape:window-x="0" + inkscape:window-y="25" /> + <metadata + id="metadata121"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:label="Layer 1" + inkscape:groupmode="layer"> + <rect + style="fill:&fill_color;;stroke:&stroke_color;;stroke-opacity:1" + id="rect904" + width="36.142857" + height="32.142857" + x="4.1428571" + y="7.1428571" /> + </g> +</svg> diff --git a/examples/JS_examples/Inherit_From_sugar.activity.Activity/po/ReadEtextsI.pot b/examples/JS_examples/Inherit_From_sugar.activity.Activity/po/ReadEtextsI.pot new file mode 100644 index 0000000..4ff74e6 --- /dev/null +++ b/examples/JS_examples/Inherit_From_sugar.activity.Activity/po/ReadEtextsI.pot @@ -0,0 +1,21 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-03-05 09:50-0600\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: activity/activity.info:2 +msgid "ReadEtexts I" +msgstr "" diff --git a/examples/JS_examples/Inherit_From_sugar.activity.Activity/setup.py b/examples/JS_examples/Inherit_From_sugar.activity.Activity/setup.py new file mode 100755 index 0000000..ebc201d --- /dev/null +++ b/examples/JS_examples/Inherit_From_sugar.activity.Activity/setup.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +# setup.py + +# Copyright (C) 2006, Red Hat, Inc. +# +# 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 + +from sugar.activity import bundlebuilder + +bundlebuilder.start() diff --git a/examples/JS_examples/Make_Standalone_Python/ReadEtexts.py b/examples/JS_examples/Make_Standalone_Python/ReadEtexts.py new file mode 100755 index 0000000..9e3522f --- /dev/null +++ b/examples/JS_examples/Make_Standalone_Python/ReadEtexts.py @@ -0,0 +1,197 @@ +#! /usr/bin/env python +# +# ReadEtexts.py Standalone version of ReadEtextsActivity.py +# Copyright (C) 2010 James D. Simmons +# +# 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 sys +import os +import zipfile +import gtk +import getopt +import pango + +page=0 +PAGE_SIZE = 45 + +class ReadEtexts(): + + def keypress_cb(self, widget, event): + "Respond when the user presses one of the arrow keys" + keyname = gtk.gdk.keyval_name(event.keyval) + if keyname == 'plus': + self.font_increase() + return True + if keyname == 'minus': + self.font_decrease() + return True + if keyname == 'Page_Up' : + self.page_previous() + return True + if keyname == 'Page_Down': + self.page_next() + return True + if keyname == 'Up' or keyname == 'KP_Up' \ + or keyname == 'KP_Left': + self.scroll_up() + return True + if keyname == 'Down' or keyname == 'KP_Down' \ + or keyname == 'KP_Right': + self.scroll_down() + return True + return False + + def page_previous(self): + global page + page=page-1 + if page < 0: page=0 + self.show_page(page) + v_adjustment = self.scrolled_window.get_vadjustment() + v_adjustment.value = v_adjustment.upper - v_adjustment.page_size + + def page_next(self): + global page + page=page+1 + if page >= len(self.page_index): page=0 + self.show_page(page) + v_adjustment = self.scrolled_window.get_vadjustment() + v_adjustment.value = v_adjustment.lower + + def font_decrease(self): + font_size = self.font_desc.get_size() / 1024 + font_size = font_size - 1 + if font_size < 1: + font_size = 1 + self.font_desc.set_size(font_size * 1024) + self.textview.modify_font(self.font_desc) + + def font_increase(self): + font_size = self.font_desc.get_size() / 1024 + font_size = font_size + 1 + self.font_desc.set_size(font_size * 1024) + self.textview.modify_font(self.font_desc) + + def scroll_down(self): + v_adjustment = self.scrolled_window.get_vadjustment() + if v_adjustment.value == v_adjustment.upper - \ + v_adjustment.page_size: + self.page_next() + return + if v_adjustment.value < v_adjustment.upper - v_adjustment.page_size: + new_value = v_adjustment.value + v_adjustment.step_increment + if new_value > v_adjustment.upper - v_adjustment.page_size: + new_value = v_adjustment.upper - v_adjustment.page_size + v_adjustment.value = new_value + + def scroll_up(self): + v_adjustment = self.scrolled_window.get_vadjustment() + if v_adjustment.value == v_adjustment.lower: + self.page_previous() + return + if v_adjustment.value > v_adjustment.lower: + new_value = v_adjustment.value - \ + v_adjustment.step_increment + if new_value < v_adjustment.lower: + new_value = v_adjustment.lower + v_adjustment.value = new_value + + def show_page(self, page_number): + global PAGE_SIZE, current_word + position = self.page_index[page_number] + self.etext_file.seek(position) + linecount = 0 + label_text = '\n\n\n' + textbuffer = self.textview.get_buffer() + while linecount < PAGE_SIZE: + line = self.etext_file.readline() + label_text = label_text + unicode(line, 'iso-8859-1') + linecount = linecount + 1 + label_text = label_text + '\n\n\n' + textbuffer.set_text(label_text) + self.textview.set_buffer(textbuffer) + + def save_extracted_file(self, zipfile, filename): + "Extract the file to a temp directory for viewing" + filebytes = zipfile.read(filename) + f = open("/tmp/" + filename, 'w') + try: + f.write(filebytes) + finally: + f.close() + + def read_file(self, filename): + "Read the Etext file" + global PAGE_SIZE + + if zipfile.is_zipfile(filename): + self.zf = zipfile.ZipFile(filename, 'r') + self.book_files = self.zf.namelist() + self.save_extracted_file(self.zf, self.book_files[0]) + currentFileName = "/tmp/" + self.book_files[0] + else: + currentFileName = filename + + self.etext_file = open(currentFileName,"r") + self.page_index = [ 0 ] + linecount = 0 + while self.etext_file: + line = self.etext_file.readline() + if not line: + break + linecount = linecount + 1 + if linecount >= PAGE_SIZE: + position = self.etext_file.tell() + self.page_index.append(position) + linecount = 0 + if filename.endswith(".zip"): + os.remove(currentFileName) + + def destroy_cb(self, widget, data=None): + gtk.main_quit() + + def main(self, file_path): + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window.connect("destroy", self.destroy_cb) + self.window.set_title("Read Etexts") + self.window.set_size_request(640, 480) + self.window.set_border_width(0) + self.read_file(file_path) + self.scrolled_window = gtk.ScrolledWindow(hadjustment=None, \ + vadjustment=None) + self.textview = gtk.TextView() + self.textview.set_editable(False) + self.textview.set_left_margin(50) + self.textview.set_cursor_visible(False) + self.textview.connect("key_press_event", self.keypress_cb) + self.font_desc = pango.FontDescription("sans 12") + self.textview.modify_font(self.font_desc) + self.show_page(0) + self.scrolled_window.add(self.textview) + self.window.add(self.scrolled_window) + self.textview.show() + self.scrolled_window.show() + self.window.show() + gtk.main() + +if __name__ == "__main__": + try: + opts, args = getopt.getopt(sys.argv[1:], "") + ReadEtexts().main(args[0]) + except getopt.error, msg: + print msg + print "This program has no options" + sys.exit(2) diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/DemoiselleActivity.py b/examples/JS_examples/Making_Activities_Using_PyGame/DemoiselleActivity.py new file mode 100644 index 0000000..2c6c99f --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/DemoiselleActivity.py @@ -0,0 +1,103 @@ +# DemoiselleActivity.py + +# Copyright (C) 2010 James D. Simmons +# +# 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 +# + +from gettext import gettext as _ + +import gtk +import pygame +from sugar.activity import activity +from sugar.graphics.toolbutton import ToolButton +import gobject +import sugargame.canvas +import demoiselle2 + +class DemoiselleActivity(activity.Activity): + def __init__(self, handle): + super(DemoiselleActivity, self).__init__(handle) + + # Build the activity toolbar. + self.build_toolbar() + + # Create the game instance. + self.game = demoiselle2.Demoiselle() + + # Build the Pygame canvas. + self._pygamecanvas = sugargame.canvas.PygameCanvas(self) + # Note that set_canvas implicitly calls read_file when resuming from the Journal. + self.set_canvas(self._pygamecanvas) + self.score = '' + + # Start the game running. + self._pygamecanvas.run_pygame(self.game.run) + + def build_toolbar(self): + toolbox = activity.ActivityToolbox(self) + activity_toolbar = toolbox.get_activity_toolbar() + activity_toolbar.keep.props.visible = False + activity_toolbar.share.props.visible = False + + self.view_toolbar = ViewToolbar() + toolbox.add_toolbar(_('View'), self.view_toolbar) + self.view_toolbar.connect('go-fullscreen', + self.view_toolbar_go_fullscreen_cb) + self.view_toolbar.show() + + toolbox.show() + self.set_toolbox(toolbox) + + def view_toolbar_go_fullscreen_cb(self, view_toolbar): + self.fullscreen() + + def read_file(self, file_path): + score_file = open(file_path, "r") + while score_file: + self.score = score_file.readline() + self.game.set_score(int(self.score)) + score_file.close() + + def write_file(self, file_path): + score = self.game.get_score() + f = open(file_path, 'wb') + try: + f.write(str(score)) + finally: + f.close + +class ViewToolbar(gtk.Toolbar): + __gtype_name__ = 'ViewToolbar' + + __gsignals__ = { + 'needs-update-size': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])), + 'go-fullscreen': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])) + } + + def __init__(self): + gtk.Toolbar.__init__(self) + self.fullscreen = ToolButton('view-fullscreen') + self.fullscreen.set_tooltip(_('Fullscreen')) + self.fullscreen.connect('clicked', self.fullscreen_cb) + self.insert(self.fullscreen, -1) + self.fullscreen.show() + + def fullscreen_cb(self, button): + self.emit('go-fullscreen') diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/MANIFEST b/examples/JS_examples/Making_Activities_Using_PyGame/MANIFEST new file mode 100644 index 0000000..42b54df --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/MANIFEST @@ -0,0 +1,17 @@ + +setup.py + + +activity/read-etexts.svg +activity/activity.info +demoiselle2.py +glider_hit.png +demoiselle.py +demoiselle.png +DemoiselleActivity.py +sky.jpg +glider_normal.png +po/Demoiselle.pot +sugargame/event.py +sugargame/__init__.py +sugargame/canvas.py diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/activity/activity.info b/examples/JS_examples/Making_Activities_Using_PyGame/activity/activity.info new file mode 100644 index 0000000..2767e12 --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/activity/activity.info @@ -0,0 +1,9 @@ +[Activity] +name = Demoiselle +service_name = net.flossmanuals.Demoiselle +icon = read-etexts +exec = sugar-activity DemoiselleActivity.DemoiselleActivity +show_launcher = yes +activity_version = 1 +license = GPLv2+ + diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/activity/activity.info~ b/examples/JS_examples/Making_Activities_Using_PyGame/activity/activity.info~ new file mode 100644 index 0000000..137333f --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/activity/activity.info~ @@ -0,0 +1,9 @@ +[Activity] +name = ReadEtexts II +service_name = net.flossmanuals.ReadETexts +icon = read-etexts +exec = sugar-activity ReadEtextsActivity4.ReadEtextsActivity +show_launcher = no +mime_types = text/plain;application/zip +activity_version = 1 +license = GPLv2+ diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/activity/read-etexts.svg b/examples/JS_examples/Making_Activities_Using_PyGame/activity/read-etexts.svg new file mode 100644 index 0000000..5682ec8 --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/activity/read-etexts.svg @@ -0,0 +1,71 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#000000"> + <!ENTITY fill_color "#FFFFFF"> +]><svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="48px" + height="48px" + id="svg116" + sodipodi:version="0.32" + inkscape:version="0.46+devel" + sodipodi:docname="New document 4" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs118"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="48 : 24 : 1" + inkscape:persp3d-origin="24 : 16 : 1" + id="perspective124" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="7" + inkscape:cx="24" + inkscape:cy="24" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:grid-bbox="true" + inkscape:document-units="px" + inkscape:window-width="1024" + inkscape:window-height="698" + inkscape:window-x="0" + inkscape:window-y="25" /> + <metadata + id="metadata121"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:label="Layer 1" + inkscape:groupmode="layer"> + <rect + style="fill:&fill_color;;stroke:&stroke_color;;stroke-opacity:1" + id="rect904" + width="36.142857" + height="32.142857" + x="4.1428571" + y="7.1428571" /> + </g> +</svg> diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/demoiselle.png b/examples/JS_examples/Making_Activities_Using_PyGame/demoiselle.png Binary files differnew file mode 100644 index 0000000..37db411 --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/demoiselle.png diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/demoiselle.py b/examples/JS_examples/Making_Activities_Using_PyGame/demoiselle.py new file mode 100755 index 0000000..93817e1 --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/demoiselle.py @@ -0,0 +1,166 @@ +#! /usr/bin/env python +# +# demoiselle.py Standalone version of DemoiselleActivity.py +# Copyright (C) 2010 James D. Simmons +# Adapted from code in the article "Rapid Game Development In +# Python" by Richard Jones. +# +# 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 pygame +import math +import sys + +class Demoiselle: + "This is a simple demonstration of using PyGame \ + sprites and collision detection." + def __init__(self): + self.background = pygame.image.load('sky.jpg') + self.screen = pygame.display.get_surface() + self.screen.blit(self.background, (0, 0)) + self.clock = pygame.time.Clock() + self.running = True + + gliders = [ + GliderSprite((200, 200)), + GliderSprite((800, 200)), + GliderSprite((200, 600)), + GliderSprite((800, 600)), + ] + self. glider_group = pygame.sprite.RenderPlain(gliders) + + def run(self): + "This method processes PyGame messages" + rect = self.screen.get_rect() + airplane = AirplaneSprite('demoiselle.png', rect.center) + airplane_sprite = pygame.sprite.RenderPlain(airplane) + + while self.running: + self.clock.tick(30) + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self.running = False + return + elif event.type == pygame.VIDEORESIZE: + pygame.display.set_mode(event.size, pygame.RESIZABLE) + self.screen.blit(self.background, (0, 0)) + + if not hasattr(event, 'key'): + continue + down = event.type == pygame.KEYDOWN + if event.key == pygame.K_DOWN or \ + event.key == pygame.K_KP2: + airplane.joystick_back = down * 5 + elif event.key == pygame.K_UP or \ + event.key == pygame.K_KP8: + airplane.joystick_forward = down * -5 + elif event.key == pygame.K_EQUALS or \ + event.key == pygame.K_KP_PLUS or \ + event.key == pygame.K_KP9: + airplane.throttle_up = down * 2 + elif event.key == pygame.K_MINUS or \ + event.key == pygame.K_KP_MINUS or \ + event.key == pygame.K_KP3: + airplane.throttle_down = down * -2 + + self.glider_group.clear(self.screen, self.background) + airplane_sprite.clear(self.screen, self.background) + collisions = pygame.sprite.spritecollide(airplane, \ + self.glider_group, False) + self.glider_group.update(collisions) + self.glider_group.draw(self.screen) + airplane_sprite.update() + airplane_sprite.draw(self.screen) + pygame.display.flip() + +class AirplaneSprite(pygame.sprite.Sprite): + "This class represents an airplane, the Demoiselle \ + created by Alberto Santos-Dumont" + MAX_FORWARD_SPEED = 10 + MIN_FORWARD_SPEED = 1 + ACCELERATION = 2 + TURN_SPEED = 5 + def __init__(self, image, position): + pygame.sprite.Sprite.__init__(self) + self.src_image = pygame.image.load(image) + self.rect = pygame.Rect(self.src_image.get_rect()) + self.position = position + self.rect.center = self.position + self.speed = 1 + self.direction = 0 + self.joystick_back = self.joystick_forward = \ + self.throttle_down = self.throttle_up = 0 + + def update(self): + "This method redraws the airplane in response\ + to events." + self.speed += (self.throttle_up + self.throttle_down) + if self.speed > self.MAX_FORWARD_SPEED: + self.speed = self.MAX_FORWARD_SPEED + if self.speed < self.MIN_FORWARD_SPEED: + self.speed = self.MIN_FORWARD_SPEED + self.direction += (self.joystick_forward + self.joystick_back) + x_coord, y_coord = self.position + rad = self.direction * math.pi / 180 + x_coord += -self.speed * math.cos(rad) + y_coord += -self.speed * math.sin(rad) + screen = pygame.display.get_surface() + if y_coord < 0: + y_coord = screen.get_height() + + if x_coord < 0: + x_coord = screen.get_width() + + if x_coord > screen.get_width(): + x_coord = 0 + + if y_coord > screen.get_height(): + y_coord = 0 + self.position = (x_coord, y_coord) + self.image = pygame.transform.rotate(self.src_image, -self.direction) + self.rect = self.image.get_rect() + self.rect.center = self.position + +class GliderSprite(pygame.sprite.Sprite): + "This class represents an individual hang glider as developed\ + by Otto Lilienthal." + def __init__(self, position): + pygame.sprite.Sprite.__init__(self) + self.normal = pygame.image.load('glider_normal.png') + self.rect = pygame.Rect(self.normal.get_rect()) + self.rect.center = position + self.image = self.normal + self.hit = pygame.image.load('glider_hit.png') + def update(self, hit_list): + "This method redraws the glider when it collides\ + with the airplane and when it is no longer \ + colliding with the airplane." + if self in hit_list: + self.image = self.hit + else: + self.image = self.normal + +def main(): + "This function is called when the game is run from the command line" + pygame.init() + pygame.display.set_mode((0, 0), pygame.RESIZABLE) + game = Demoiselle() + game.run() + sys.exit(0) + +if __name__ == '__main__': + main() diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/demoiselle2.py b/examples/JS_examples/Making_Activities_Using_PyGame/demoiselle2.py new file mode 100755 index 0000000..46656ca --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/demoiselle2.py @@ -0,0 +1,185 @@ +#! /usr/bin/env python +# +# demoiselle2.py + +# This is a modified version of demoiselle.py that will be run +# by DemoiselleActivity.py using SugarGame. + +# Copyright (C) 2010 James D. Simmons +# Adapted from code in the article "Rapid Game Development In +# Python" by Richard Jones. +# +# 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 pygame +import gtk +import math +import sys + +class Demoiselle: + "This is a simple demonstration of using PyGame \ + sprites and collision detection." + def __init__(self): + self.clock = pygame.time.Clock() + self.running = True + self.background = pygame.image.load('sky.jpg') + self.score = 99 + + def get_score(self): + return self.score + + def set_score(self, score): + self.score = score + + def run(self): + "This method processes PyGame messages" + + screen = pygame.display.get_surface() + screen.blit(self.background, (0, 0)) + + gliders = [ + GliderSprite((200, 200)), + GliderSprite((800, 200)), + GliderSprite((200, 600)), + GliderSprite((800, 600)), + ] + glider_group = pygame.sprite.RenderPlain(gliders) + + rect = screen.get_rect() + airplane = AirplaneSprite('demoiselle.png', rect.center) + airplane_sprite = pygame.sprite.RenderPlain(airplane) + + while self.running: + self.clock.tick(30) + + # Pump GTK messages. + while gtk.events_pending(): + gtk.main_iteration() + + # Pump PyGame messages. + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self.running = False + return + elif event.type == pygame.VIDEORESIZE: + pygame.display.set_mode(event.size, pygame.RESIZABLE) + screen.blit(self.background, (0, 0)) + + if not hasattr(event, 'key'): + continue + down = event.type == pygame.KEYDOWN + if event.key == pygame.K_DOWN or \ + event.key == pygame.K_KP2: + airplane.joystick_back = down * 5 + elif event.key == pygame.K_UP or \ + event.key == pygame.K_KP8: + airplane.joystick_forward = down * -5 + elif event.key == pygame.K_EQUALS or \ + event.key == pygame.K_KP_PLUS or \ + event.key == pygame.K_KP9: + airplane.throttle_up = down * 2 + elif event.key == pygame.K_MINUS or \ + event.key == pygame.K_KP_MINUS or \ + event.key == pygame.K_KP3: + airplane.throttle_down = down * -2 + + glider_group.clear(screen, self.background) + airplane_sprite.clear(screen, self.background) + collisions = pygame.sprite.spritecollide(airplane, \ + glider_group, False) + glider_group.update(collisions) + glider_group.draw(screen) + airplane_sprite.update() + airplane_sprite.draw(screen) + pygame.display.flip() + +class AirplaneSprite(pygame.sprite.Sprite): + "This class represents an airplane, the Demoiselle \ + created by Alberto Santos-Dumont" + MAX_FORWARD_SPEED = 10 + MIN_FORWARD_SPEED = 1 + ACCELERATION = 2 + TURN_SPEED = 5 + def __init__(self, image, position): + pygame.sprite.Sprite.__init__(self) + self.src_image = pygame.image.load(image) + self.rect = pygame.Rect(self.src_image.get_rect()) + self.position = position + self.rect.center = self.position + self.speed = 1 + self.direction = 0 + self.joystick_back = self.joystick_forward = \ + self.throttle_down = self.throttle_up = 0 + + def update(self): + "This method redraws the airplane in response\ + to events." + self.speed += (self.throttle_up + self.throttle_down) + if self.speed > self.MAX_FORWARD_SPEED: + self.speed = self.MAX_FORWARD_SPEED + if self.speed < self.MIN_FORWARD_SPEED: + self.speed = self.MIN_FORWARD_SPEED + self.direction += (self.joystick_forward + self.joystick_back) + x_coord, y_coord = self.position + rad = self.direction * math.pi / 180 + x_coord += -self.speed * math.cos(rad) + y_coord += -self.speed * math.sin(rad) + screen = pygame.display.get_surface() + if y_coord < 0: + y_coord = screen.get_height() + + if x_coord < 0: + x_coord = screen.get_width() + + if x_coord > screen.get_width(): + x_coord = 0 + + if y_coord > screen.get_height(): + y_coord = 0 + self.position = (x_coord, y_coord) + self.image = pygame.transform.rotate(self.src_image, -self.direction) + self.rect = self.image.get_rect() + self.rect.center = self.position + +class GliderSprite(pygame.sprite.Sprite): + "This class represents an individual hang glider as developed\ + by Otto Lilienthal." + def __init__(self, position): + pygame.sprite.Sprite.__init__(self) + self.normal = pygame.image.load('glider_normal.png') + self.rect = pygame.Rect(self.normal.get_rect()) + self.rect.center = position + self.image = self.normal + self.hit = pygame.image.load('glider_hit.png') + def update(self, hit_list): + "This method redraws the glider when it collides\ + with the airplane and when it is no longer \ + colliding with the airplane." + if self in hit_list: + self.image = self.hit + else: + self.image = self.normal + +def main(): + "This function is called when the game is run from the command line" + pygame.init() + pygame.display.set_mode((0, 0), pygame.RESIZABLE) + game = Demoiselle() + game.run() + sys.exit(0) + +if __name__ == '__main__': + main() diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/glider_hit.png b/examples/JS_examples/Making_Activities_Using_PyGame/glider_hit.png Binary files differnew file mode 100644 index 0000000..49f0a8c --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/glider_hit.png diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/glider_normal.png b/examples/JS_examples/Making_Activities_Using_PyGame/glider_normal.png Binary files differnew file mode 100644 index 0000000..8fa812b --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/glider_normal.png diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/po/Demoiselle.pot b/examples/JS_examples/Making_Activities_Using_PyGame/po/Demoiselle.pot new file mode 100644 index 0000000..e50ab51 --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/po/Demoiselle.pot @@ -0,0 +1,3 @@ +#: activity/activity.info:2 +msgid "Demoiselle" +msgstr "" diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/setup.py b/examples/JS_examples/Making_Activities_Using_PyGame/setup.py new file mode 100755 index 0000000..ebc201d --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/setup.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +# setup.py + +# Copyright (C) 2006, Red Hat, Inc. +# +# 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 + +from sugar.activity import bundlebuilder + +bundlebuilder.start() diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/sky.jpg b/examples/JS_examples/Making_Activities_Using_PyGame/sky.jpg Binary files differnew file mode 100644 index 0000000..a92cf6c --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/sky.jpg diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/sugargame/__init__.py b/examples/JS_examples/Making_Activities_Using_PyGame/sugargame/__init__.py new file mode 100644 index 0000000..7e49527 --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/sugargame/__init__.py @@ -0,0 +1 @@ +__version__ = '1.0' diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/sugargame/canvas.py b/examples/JS_examples/Making_Activities_Using_PyGame/sugargame/canvas.py new file mode 100644 index 0000000..cf99a13 --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/sugargame/canvas.py @@ -0,0 +1,56 @@ +import os +import gtk +import gobject +import pygame +import event + +CANVAS = None + +class PygameCanvas(gtk.EventBox): + def __init__(self, mainwindow): + gtk.EventBox.__init__(self) + + global CANVAS + assert CANVAS == None, "Only one PygameCanvas can be created, ever." + CANVAS = self + + self._mainwindow = mainwindow + + self.set_flags(gtk.CAN_FOCUS) + + self._socket = gtk.Socket() + self.add(self._socket) + self.show_all() + + def run_pygame(self, main_fn): + # Run the main loop after a short delay. The reason for the delay is that the + # Sugar activity is not properly created until after its constructor returns. + # If the Pygame main loop is called from the activity constructor, the + # constructor never returns and the activity freezes. + gobject.idle_add(self._run_pygame_cb, main_fn) + + def _run_pygame_cb(self, main_fn): + assert pygame.display.get_surface() is None, "PygameCanvas.run_pygame can only be called once." + + # Preinitialize Pygame with the X window ID. + assert pygame.display.get_init() == False, "Pygame must not be initialized before calling PygameCanvas.run_pygame." + os.environ['SDL_WINDOWID'] = str(self._socket.get_id()) + pygame.init() + + # Restore the default cursor. + self._socket.get_window().set_cursor(None) + + # Initialize the Pygame window. + r = self.get_allocation() + pygame.display.set_mode((r.width, r.height), pygame.RESIZABLE) + + # Hook certain Pygame functions with GTK equivalents. + translator = event.Translator(self._mainwindow, self) + translator.hook_pygame() + + # Run the Pygame main loop. + main_fn() + return False + + def get_pygame_widget(self): + return self._socket diff --git a/examples/JS_examples/Making_Activities_Using_PyGame/sugargame/event.py b/examples/JS_examples/Making_Activities_Using_PyGame/sugargame/event.py new file mode 100644 index 0000000..52ca4ab --- /dev/null +++ b/examples/JS_examples/Making_Activities_Using_PyGame/sugargame/event.py @@ -0,0 +1,241 @@ +import gtk +import gobject +import pygame +import pygame.event +import logging + +class _MockEvent(object): + def __init__(self, keyval): + self.keyval = keyval + +class Translator(object): + key_trans = { + 'Alt_L': pygame.K_LALT, + 'Alt_R': pygame.K_RALT, + 'Control_L': pygame.K_LCTRL, + 'Control_R': pygame.K_RCTRL, + 'Shift_L': pygame.K_LSHIFT, + 'Shift_R': pygame.K_RSHIFT, + 'Super_L': pygame.K_LSUPER, + 'Super_R': pygame.K_RSUPER, + 'KP_Page_Up' : pygame.K_KP9, + 'KP_Page_Down' : pygame.K_KP3, + 'KP_End' : pygame.K_KP1, + 'KP_Home' : pygame.K_KP7, + 'KP_Up' : pygame.K_KP8, + 'KP_Down' : pygame.K_KP2, + 'KP_Left' : pygame.K_KP4, + 'KP_Right' : pygame.K_KP6, + + } + + mod_map = { + pygame.K_LALT: pygame.KMOD_LALT, + pygame.K_RALT: pygame.KMOD_RALT, + pygame.K_LCTRL: pygame.KMOD_LCTRL, + pygame.K_RCTRL: pygame.KMOD_RCTRL, + pygame.K_LSHIFT: pygame.KMOD_LSHIFT, + pygame.K_RSHIFT: pygame.KMOD_RSHIFT, + } + + def __init__(self, mainwindow, inner_evb): + """Initialise the Translator with the windows to which to listen""" + self._mainwindow = mainwindow + self._inner_evb = inner_evb + + # Enable events + self._mainwindow.set_events( + gtk.gdk.KEY_PRESS_MASK | \ + gtk.gdk.KEY_RELEASE_MASK \ + ) + + self._inner_evb.set_events( + gtk.gdk.POINTER_MOTION_MASK | \ + gtk.gdk.POINTER_MOTION_HINT_MASK | \ + gtk.gdk.BUTTON_MOTION_MASK | \ + gtk.gdk.BUTTON_PRESS_MASK | \ + gtk.gdk.BUTTON_RELEASE_MASK + ) + + self._mainwindow.set_flags(gtk.CAN_FOCUS) + self._inner_evb.set_flags(gtk.CAN_FOCUS) + + # Callback functions to link the event systems + self._mainwindow.connect('unrealize', self._quit_cb) + self._inner_evb.connect('key_press_event', self._keydown_cb) + self._inner_evb.connect('key_release_event', self._keyup_cb) + self._inner_evb.connect('button_press_event', self._mousedown_cb) + self._inner_evb.connect('button_release_event', self._mouseup_cb) + self._inner_evb.connect('motion-notify-event', self._mousemove_cb) + self._inner_evb.connect('expose-event', self._expose_cb) + self._inner_evb.connect('configure-event', self._resize_cb) + + # Internal data + self.__stopped = False + self.__keystate = [0] * 323 + self.__button_state = [0,0,0] + self.__mouse_pos = (0,0) + self.__repeat = (None, None) + self.__held = set() + self.__held_time_left = {} + self.__held_last_time = {} + self.__tick_id = None + + def hook_pygame(self): + pygame.key.get_pressed = self._get_pressed + pygame.key.set_repeat = self._set_repeat + pygame.mouse.get_pressed = self._get_mouse_pressed + pygame.mouse.get_pos = self._get_mouse_pos + + def _expose_cb(self, event, widget): + pygame.event.post(pygame.event.Event(pygame.VIDEOEXPOSE)) + return True + + def _resize_cb(self, widget, event): + evt = pygame.event.Event(pygame.VIDEORESIZE, + size=(event.width,event.height), width=event.width, height=event.height) + pygame.event.post(evt) + return False # continue processing + + def _quit_cb(self, data=None): + self.__stopped = True + pygame.event.post(pygame.event.Event(pygame.QUIT)) + + def _keydown_cb(self, widget, event): + key = event.keyval + if key in self.__held: + return True + else: + if self.__repeat[0] is not None: + self.__held_last_time[key] = pygame.time.get_ticks() + self.__held_time_left[key] = self.__repeat[0] + self.__held.add(key) + + return self._keyevent(widget, event, pygame.KEYDOWN) + + def _keyup_cb(self, widget, event): + key = event.keyval + if self.__repeat[0] is not None: + if key in self.__held: + # This is possibly false if set_repeat() is called with a key held + del self.__held_time_left[key] + del self.__held_last_time[key] + self.__held.discard(key) + + return self._keyevent(widget, event, pygame.KEYUP) + + def _keymods(self): + mod = 0 + for key_val, mod_val in self.mod_map.iteritems(): + mod |= self.__keystate[key_val] and mod_val + return mod + + def _keyevent(self, widget, event, type): + key = gtk.gdk.keyval_name(event.keyval) + if key is None: + # No idea what this key is. + return False + + keycode = None + if key in self.key_trans: + keycode = self.key_trans[key] + elif hasattr(pygame, 'K_'+key.upper()): + keycode = getattr(pygame, 'K_'+key.upper()) + elif hasattr(pygame, 'K_'+key.lower()): + keycode = getattr(pygame, 'K_'+key.lower()) + elif key == 'XF86Start': + # view source request, specially handled... + self._mainwindow.view_source() + else: + print 'Key %s unrecognized' % key + + if keycode is not None: + if type == pygame.KEYDOWN: + mod = self._keymods() + self.__keystate[keycode] = type == pygame.KEYDOWN + if type == pygame.KEYUP: + mod = self._keymods() + ukey = unichr(gtk.gdk.keyval_to_unicode(event.keyval)) + if ukey == '\000': + ukey = '' + evt = pygame.event.Event(type, key=keycode, unicode=ukey, mod=mod) + self._post(evt) + + return True + + def _get_pressed(self): + return self.__keystate + + def _get_mouse_pressed(self): + return self.__button_state + + def _mousedown_cb(self, widget, event): + self.__button_state[event.button-1] = 1 + return self._mouseevent(widget, event, pygame.MOUSEBUTTONDOWN) + + def _mouseup_cb(self, widget, event): + self.__button_state[event.button-1] = 0 + return self._mouseevent(widget, event, pygame.MOUSEBUTTONUP) + + def _mouseevent(self, widget, event, type): + evt = pygame.event.Event(type, button=event.button, pos=(event.x, event.y)) + self._post(evt) + return True + + def _mousemove_cb(self, widget, event): + # From http://www.learningpython.com/2006/07/25/writing-a-custom-widget-using-pygtk/ + # if this is a hint, then let's get all the necessary + # information, if not it's all we need. + if event.is_hint: + x, y, state = event.window.get_pointer() + else: + x = event.x + y = event.y + state = event.state + + rel = (x - self.__mouse_pos[0], y - self.__mouse_pos[1]) + self.__mouse_pos = (x, y) + + self.__button_state = [ + state & gtk.gdk.BUTTON1_MASK and 1 or 0, + state & gtk.gdk.BUTTON2_MASK and 1 or 0, + state & gtk.gdk.BUTTON3_MASK and 1 or 0, + ] + + evt = pygame.event.Event(pygame.MOUSEMOTION, + pos=self.__mouse_pos, rel=rel, buttons=self.__button_state) + self._post(evt) + return True + + def _tick_cb(self): + cur_time = pygame.time.get_ticks() + for key in self.__held: + delta = cur_time - self.__held_last_time[key] + self.__held_last_time[key] = cur_time + + self.__held_time_left[key] -= delta + if self.__held_time_left[key] <= 0: + self.__held_time_left[key] = self.__repeat[1] + self._keyevent(None, _MockEvent(key), pygame.KEYDOWN) + + return True + + def _set_repeat(self, delay=None, interval=None): + if delay is not None and self.__repeat[0] is None: + self.__tick_id = gobject.timeout_add(10, self._tick_cb) + elif delay is None and self.__repeat[0] is not None: + gobject.source_remove(self.__tick_id) + self.__repeat = (delay, interval) + + def _get_mouse_pos(self): + return self.__mouse_pos + + def _post(self, evt): + try: + pygame.event.post(evt) + except pygame.error, e: + if str(e) == 'Event queue full': + print "Event queue full!" + pass + else: + raise e diff --git a/examples/JS_examples/Making_Shared_Activities/MANIFEST b/examples/JS_examples/Making_Shared_Activities/MANIFEST new file mode 100644 index 0000000..a46aae0 --- /dev/null +++ b/examples/JS_examples/Making_Shared_Activities/MANIFEST @@ -0,0 +1,8 @@ +toolbar.py +setup.py + + +activity/read-etexts.svg +activity/activity.info +ReadEtextsActivity3.py +po/ReadEtextsIII.pot diff --git a/examples/JS_examples/Making_Shared_Activities/ReadEtextsActivity3.py b/examples/JS_examples/Making_Shared_Activities/ReadEtextsActivity3.py new file mode 100644 index 0000000..5513e2a --- /dev/null +++ b/examples/JS_examples/Making_Shared_Activities/ReadEtextsActivity3.py @@ -0,0 +1,640 @@ +# +# ReadEtextsActivity3.py A version of ReadEtextsActivity that supports +# sharing ebooks over a Stream Tube in Telepathy. +# Copyright (C) 2010 James D. Simmons +# +# 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 sys +import os +import logging +import tempfile +import time +import zipfile +import pygtk +import gtk +import pango +import dbus +import gobject +import telepathy +from sugar.activity import activity +from sugar.graphics import style +from sugar import network +from sugar.datastore import datastore +from sugar.graphics.alert import NotifyAlert +from toolbar import ReadToolbar, ViewToolbar +from gettext import gettext as _ + +page=0 +PAGE_SIZE = 45 +TOOLBAR_READ = 2 + +logger = logging.getLogger('read-etexts2-activity') + +class ReadHTTPRequestHandler(network.ChunkedGlibHTTPRequestHandler): + """HTTP Request Handler for transferring document while collaborating. + + RequestHandler class that integrates with Glib mainloop. It writes + the specified file to the client in chunks, returning control to the + mainloop between chunks. + + """ + def translate_path(self, path): + """Return the filepath to the shared document.""" + return self.server.filepath + + +class ReadHTTPServer(network.GlibTCPServer): + """HTTP Server for transferring document while collaborating.""" + def __init__(self, server_address, filepath): + """Set up the GlibTCPServer with the ReadHTTPRequestHandler. + + filepath -- path to shared document to be served. + """ + self.filepath = filepath + network.GlibTCPServer.__init__(self, server_address, + ReadHTTPRequestHandler) + + +class ReadURLDownloader(network.GlibURLDownloader): + """URLDownloader that provides content-length and content-type.""" + + def get_content_length(self): + """Return the content-length of the download.""" + if self._info is not None: + return int(self._info.headers.get('Content-Length')) + + def get_content_type(self): + """Return the content-type of the download.""" + if self._info is not None: + return self._info.headers.get('Content-type') + return None + +READ_STREAM_SERVICE = 'read-etexts-activity-http' + +class ReadEtextsActivity(activity.Activity): + def __init__(self, handle): + "The entry point to the Activity" + global page + activity.Activity.__init__(self, handle) + + self.fileserver = None + self.object_id = handle.object_id + + toolbox = activity.ActivityToolbox(self) + activity_toolbar = toolbox.get_activity_toolbar() + activity_toolbar.keep.props.visible = False + + self.edit_toolbar = activity.EditToolbar() + self.edit_toolbar.undo.props.visible = False + self.edit_toolbar.redo.props.visible = False + self.edit_toolbar.separator.props.visible = False + self.edit_toolbar.copy.set_sensitive(False) + self.edit_toolbar.copy.connect('clicked', self.edit_toolbar_copy_cb) + self.edit_toolbar.paste.props.visible = False + toolbox.add_toolbar(_('Edit'), self.edit_toolbar) + self.edit_toolbar.show() + + self.read_toolbar = ReadToolbar() + toolbox.add_toolbar(_('Read'), self.read_toolbar) + self.read_toolbar.back.connect('clicked', self.go_back_cb) + self.read_toolbar.forward.connect('clicked', self.go_forward_cb) + self.read_toolbar.num_page_entry.connect('activate', + self.num_page_entry_activate_cb) + self.read_toolbar.show() + + self.view_toolbar = ViewToolbar() + toolbox.add_toolbar(_('View'), self.view_toolbar) + self.view_toolbar.connect('go-fullscreen', + self.view_toolbar_go_fullscreen_cb) + self.view_toolbar.zoom_in.connect('clicked', self.zoom_in_cb) + self.view_toolbar.zoom_out.connect('clicked', self.zoom_out_cb) + self.view_toolbar.show() + + self.set_toolbox(toolbox) + toolbox.show() + self.scrolled_window = gtk.ScrolledWindow() + self.scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self.scrolled_window.props.shadow_type = gtk.SHADOW_NONE + + self.textview = gtk.TextView() + self.textview.set_editable(False) + self.textview.set_cursor_visible(False) + self.textview.set_left_margin(50) + self.textview.connect("key_press_event", self.keypress_cb) + + self.progressbar = gtk.ProgressBar() + self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT) + self.progressbar.set_fraction(0.0) + + self.scrolled_window.add(self.textview) + self.textview.show() + self.scrolled_window.show() + + vbox = gtk.VBox() + vbox.pack_start(self.progressbar, False, False, 10) + vbox.pack_start(self.scrolled_window) + self.set_canvas(vbox) + vbox.show() + + page = 0 + self.clipboard = gtk.Clipboard(display=gtk.gdk.display_get_default(), \ + selection="CLIPBOARD") + self.textview.grab_focus() + self.font_desc = pango.FontDescription("sans %d" % style.zoom(10)) + self.textview.modify_font(self.font_desc) + + buffer = self.textview.get_buffer() + self.markset_id = buffer.connect("mark-set", self.mark_set_cb) + + self.toolbox.set_current_toolbar(TOOLBAR_READ) + self.unused_download_tubes = set() + self.want_document = True + self.download_content_length = 0 + self.download_content_type = None + # Status of temp file used for write_file: + self.tempfile = None + self.close_requested = False + self.connect("shared", self.shared_cb) + + self.is_received_document = False + + if self._shared_activity and handle.object_id == None: + # We're joining, and we don't already have the document. + if self.get_shared(): + # Already joined for some reason, just get the document + self.joined_cb(self) + else: + # Wait for a successful join before trying to get the document + self.connect("joined", self.joined_cb) + + def keypress_cb(self, widget, event): + "Respond when the user presses one of the arrow keys" + keyname = gtk.gdk.keyval_name(event.keyval) + print keyname + if keyname == 'plus': + self.font_increase() + return True + if keyname == 'minus': + self.font_decrease() + return True + if keyname == 'Page_Up' : + self.page_previous() + return True + if keyname == 'Page_Down': + self.page_next() + return True + if keyname == 'Up' or keyname == 'KP_Up' \ + or keyname == 'KP_Left': + self.scroll_up() + return True + if keyname == 'Down' or keyname == 'KP_Down' \ + or keyname == 'KP_Right': + self.scroll_down() + return True + return False + + def num_page_entry_activate_cb(self, entry): + global page + if entry.props.text: + new_page = int(entry.props.text) - 1 + else: + new_page = 0 + + if new_page >= self.read_toolbar.total_pages: + new_page = self.read_toolbar.total_pages - 1 + elif new_page < 0: + new_page = 0 + + self.read_toolbar.current_page = new_page + self.read_toolbar.set_current_page(new_page) + self.show_page(new_page) + entry.props.text = str(new_page + 1) + self.read_toolbar.update_nav_buttons() + page = new_page + + def go_back_cb(self, button): + self.page_previous() + + def go_forward_cb(self, button): + self.page_next() + + def page_previous(self): + global page + page=page-1 + if page < 0: page=0 + self.read_toolbar.set_current_page(page) + self.show_page(page) + v_adjustment = self.scrolled_window.get_vadjustment() + v_adjustment.value = v_adjustment.upper - v_adjustment.page_size + + def page_next(self): + global page + page=page+1 + if page >= len(self.page_index): page=0 + self.read_toolbar.set_current_page(page) + self.show_page(page) + v_adjustment = self.scrolled_window.get_vadjustment() + v_adjustment.value = v_adjustment.lower + + def zoom_in_cb(self, button): + self.font_increase() + + def zoom_out_cb(self, button): + self.font_decrease() + + def font_decrease(self): + font_size = self.font_desc.get_size() / 1024 + font_size = font_size - 1 + if font_size < 1: + font_size = 1 + self.font_desc.set_size(font_size * 1024) + self.textview.modify_font(self.font_desc) + + def font_increase(self): + font_size = self.font_desc.get_size() / 1024 + font_size = font_size + 1 + self.font_desc.set_size(font_size * 1024) + self.textview.modify_font(self.font_desc) + + def mark_set_cb(self, textbuffer, iter, textmark): + + if textbuffer.get_has_selection(): + begin, end = textbuffer.get_selection_bounds() + self.edit_toolbar.copy.set_sensitive(True) + else: + self.edit_toolbar.copy.set_sensitive(False) + + def edit_toolbar_copy_cb(self, button): + textbuffer = self.textview.get_buffer() + begin, end = textbuffer.get_selection_bounds() + copy_text = textbuffer.get_text(begin, end) + self.clipboard.set_text(copy_text) + + def view_toolbar_go_fullscreen_cb(self, view_toolbar): + self.fullscreen() + + def scroll_down(self): + v_adjustment = self.scrolled_window.get_vadjustment() + if v_adjustment.value == v_adjustment.upper - \ + v_adjustment.page_size: + self.page_next() + return + if v_adjustment.value < v_adjustment.upper - \ + v_adjustment.page_size: + new_value = v_adjustment.value + v_adjustment.step_increment + if new_value > v_adjustment.upper - v_adjustment.page_size: + new_value = v_adjustment.upper - v_adjustment.page_size + v_adjustment.value = new_value + + def scroll_up(self): + v_adjustment = self.scrolled_window.get_vadjustment() + if v_adjustment.value == v_adjustment.lower: + self.page_previous() + return + if v_adjustment.value > v_adjustment.lower: + new_value = v_adjustment.value - \ + v_adjustment.step_increment + if new_value < v_adjustment.lower: + new_value = v_adjustment.lower + v_adjustment.value = new_value + + def show_page(self, page_number): + global PAGE_SIZE, current_word + position = self.page_index[page_number] + self.etext_file.seek(position) + linecount = 0 + label_text = '\n\n\n' + textbuffer = self.textview.get_buffer() + while linecount < PAGE_SIZE: + line = self.etext_file.readline() + label_text = label_text + unicode(line, 'iso-8859-1') + linecount = linecount + 1 + label_text = label_text + '\n\n\n' + textbuffer.set_text(label_text) + self.textview.set_buffer(textbuffer) + + def save_extracted_file(self, zipfile, filename): + "Extract the file to a temp directory for viewing" + filebytes = zipfile.read(filename) + outfn = self.make_new_filename(filename) + if (outfn == ''): + return False + f = open(os.path.join(self.get_activity_root(), 'tmp', outfn), 'w') + try: + f.write(filebytes) + finally: + f.close() + + def get_saved_page_number(self): + global page + title = self.metadata.get('title', '') + if title == '' or not title[len(title)- 1].isdigit(): + page = 0 + else: + i = len(title) - 1 + newPage = '' + while (title[i].isdigit() and i > 0): + newPage = title[i] + newPage + i = i - 1 + if title[i] == 'P': + page = int(newPage) - 1 + else: + # not a page number; maybe a volume number. + page = 0 + + def save_page_number(self): + global page + title = self.metadata.get('title', '') + if title == '' or not title[len(title)- 1].isdigit(): + title = title + ' P' + str(page + 1) + else: + i = len(title) - 1 + while (title[i].isdigit() and i > 0): + i = i - 1 + if title[i] == 'P': + title = title[0:i] + 'P' + str(page + 1) + else: + title = title + ' P' + str(page + 1) + self.metadata['title'] = title + + def read_file(self, filename): + "Read the Etext file" + global PAGE_SIZE, page + + tempfile = os.path.join(self.get_activity_root(), 'instance', \ + 'tmp%i' % time.time()) + os.link(filename, tempfile) + self.tempfile = tempfile + + if zipfile.is_zipfile(filename): + self.zf = zipfile.ZipFile(filename, 'r') + self.book_files = self.zf.namelist() + self.save_extracted_file(self.zf, self.book_files[0]) + currentFileName = os.path.join(self.get_activity_root(), \ + 'tmp', self.book_files[0]) + else: + currentFileName = filename + + self.etext_file = open(currentFileName,"r") + self.page_index = [ 0 ] + pagecount = 0 + linecount = 0 + while self.etext_file: + line = self.etext_file.readline() + if not line: + break + linecount = linecount + 1 + if linecount >= PAGE_SIZE: + position = self.etext_file.tell() + self.page_index.append(position) + linecount = 0 + pagecount = pagecount + 1 + if filename.endswith(".zip"): + os.remove(currentFileName) + self.get_saved_page_number() + self.show_page(page) + self.read_toolbar.set_total_pages(pagecount + 1) + self.read_toolbar.set_current_page(page) + + # We've got the document, so if we're a shared activity, offer it + if self.get_shared(): + self.watch_for_tubes() + self.share_document() + + def make_new_filename(self, filename): + partition_tuple = filename.rpartition('/') + return partition_tuple[2] + + def write_file(self, filename): + "Save meta data for the file." + if self.is_received_document: + # This document was given to us by someone, so we have + # to save it to the Journal. + self.etext_file.seek(0) + filebytes = self.etext_file.read() + f = open(filename, 'wb') + try: + f.write(filebytes) + finally: + f.close() + elif self.tempfile: + if self.close_requested: + os.link(self.tempfile, filename) + logger.debug("Removing temp file %s because we will close", \ + self.tempfile) + os.unlink(self.tempfile) + self.tempfile = None + else: + # skip saving empty file + raise NotImplementedError + + self.metadata['activity'] = self.get_bundle_id() + self.save_page_number() + + def can_close(self): + self.close_requested = True + return True + + def joined_cb(self, also_self): + """Callback for when a shared activity is joined. + + Get the shared document from another participant. + """ + self.watch_for_tubes() + gobject.idle_add(self.get_document) + + def get_document(self): + if not self.want_document: + return False + + # Assign a file path to download if one doesn't exist yet + if not self._jobject.file_path: + path = os.path.join(self.get_activity_root(), 'instance', + 'tmp%i' % time.time()) + else: + path = self._jobject.file_path + + # Pick an arbitrary tube we can try to download the document from + try: + tube_id = self.unused_download_tubes.pop() + except (ValueError, KeyError), e: + logger.debug('No tubes to get the document from right now: %s', + e) + return False + + # Avoid trying to download the document multiple times at once + self.want_document = False + gobject.idle_add(self.download_document, tube_id, path) + return False + + def download_document(self, tube_id, path): + chan = self._shared_activity.telepathy_tubes_chan + iface = chan[telepathy.CHANNEL_TYPE_TUBES] + addr = iface.AcceptStreamTube(tube_id, + telepathy.SOCKET_ADDRESS_TYPE_IPV4, + telepathy.SOCKET_ACCESS_CONTROL_LOCALHOST, 0, + utf8_strings=True) + logger.debug('Accepted stream tube: listening address is %r', \ + addr) + assert isinstance(addr, dbus.Struct) + assert len(addr) == 2 + assert isinstance(addr[0], str) + assert isinstance(addr[1], (int, long)) + assert addr[1] > 0 and addr[1] < 65536 + port = int(addr[1]) + + self.progressbar.show() + getter = ReadURLDownloader("http://%s:%d/document" + % (addr[0], port)) + getter.connect("finished", self.download_result_cb, tube_id) + getter.connect("progress", self.download_progress_cb, tube_id) + getter.connect("error", self.download_error_cb, tube_id) + logger.debug("Starting download to %s...", path) + getter.start(path) + self.download_content_length = getter.get_content_length() + self.download_content_type = getter.get_content_type() + return False + + def download_progress_cb(self, getter, bytes_downloaded, tube_id): + if self.download_content_length > 0: + logger.debug("Downloaded %u of %u bytes from tube %u...", + bytes_downloaded, self.download_content_length, + tube_id) + else: + logger.debug("Downloaded %u bytes from tube %u...", + bytes_downloaded, tube_id) + total = self.download_content_length + self.set_downloaded_bytes(bytes_downloaded, total) + gtk.gdk.threads_enter() + while gtk.events_pending(): + gtk.main_iteration() + gtk.gdk.threads_leave() + + def set_downloaded_bytes(self, bytes, total): + fraction = float(bytes) / float(total) + self.progressbar.set_fraction(fraction) + logger.debug("Downloaded percent", fraction) + + def clear_downloaded_bytes(self): + self.progressbar.set_fraction(0.0) + logger.debug("Cleared download bytes") + + def download_error_cb(self, getter, err, tube_id): + self.progressbar.hide() + logger.debug("Error getting document from tube %u: %s", + tube_id, err) + self.alert(_('Failure'), _('Error getting document from tube')) + self.want_document = True + self.download_content_length = 0 + self.download_content_type = None + gobject.idle_add(self.get_document) + + def download_result_cb(self, getter, tempfile, suggested_name, tube_id): + if self.download_content_type.startswith('text/html'): + # got an error page instead + self.download_error_cb(getter, 'HTTP Error', tube_id) + return + + del self.unused_download_tubes + + self.tempfile = tempfile + file_path = os.path.join(self.get_activity_root(), 'instance', + '%i' % time.time()) + logger.debug("Saving file %s to datastore...", file_path) + os.link(tempfile, file_path) + self._jobject.file_path = file_path + datastore.write(self._jobject, transfer_ownership=True) + + logger.debug("Got document %s (%s) from tube %u", + tempfile, suggested_name, tube_id) + self.is_received_document = True + self.read_file(tempfile) + self.save() + self.progressbar.hide() + + def shared_cb(self, activityid): + """Callback when activity shared. + + Set up to share the document. + + """ + # We initiated this activity and have now shared it, so by + # definition we have the file. + logger.debug('Activity became shared') + self.watch_for_tubes() + self.share_document() + + def share_document(self): + """Share the document.""" + h = hash(self._activity_id) + port = 1024 + (h % 64511) + logger.debug('Starting HTTP server on port %d', port) + self.fileserver = ReadHTTPServer(("", port), + self.tempfile) + + # Make a tube for it + chan = self._shared_activity.telepathy_tubes_chan + iface = chan[telepathy.CHANNEL_TYPE_TUBES] + self.fileserver_tube_id = iface.OfferStreamTube(READ_STREAM_SERVICE, + {}, + telepathy.SOCKET_ADDRESS_TYPE_IPV4, + ('127.0.0.1', dbus.UInt16(port)), + telepathy.SOCKET_ACCESS_CONTROL_LOCALHOST, 0) + + def watch_for_tubes(self): + """Watch for new tubes.""" + tubes_chan = self._shared_activity.telepathy_tubes_chan + + tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube', + self.new_tube_cb) + tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes( + reply_handler=self.list_tubes_reply_cb, + error_handler=self.list_tubes_error_cb) + + def new_tube_cb(self, tube_id, initiator, tube_type, service, params, + state): + """Callback when a new tube becomes available.""" + logger.debug('New tube: ID=%d initator=%d type=%d service=%s ' + 'params=%r state=%d', tube_id, initiator, tube_type, + service, params, state) + if service == READ_STREAM_SERVICE: + logger.debug('I could download from that tube') + self.unused_download_tubes.add(tube_id) + # if no download is in progress, let's fetch the document + if self.want_document: + gobject.idle_add(self.get_document) + + def list_tubes_reply_cb(self, tubes): + """Callback when new tubes are available.""" + for tube_info in tubes: + self.new_tube_cb(*tube_info) + + def list_tubes_error_cb(self, e): + """Handle ListTubes error by logging.""" + logger.error('ListTubes() failed: %s', e) + + def alert(self, title, text=None): + alert = NotifyAlert(timeout=20) + alert.props.title = title + alert.props.msg = text + self.add_alert(alert) + alert.connect('response', self.alert_cancel_cb) + alert.show() + + def alert_cancel_cb(self, alert, response_id): + self.remove_alert(alert) + self.textview.grab_focus() diff --git a/examples/JS_examples/Making_Shared_Activities/activity/activity.info b/examples/JS_examples/Making_Shared_Activities/activity/activity.info new file mode 100644 index 0000000..0add223 --- /dev/null +++ b/examples/JS_examples/Making_Shared_Activities/activity/activity.info @@ -0,0 +1,9 @@ +[Activity] +name = ReadEtexts III +service_name = net.flossmanuals.ReadETextsActivity3 +icon = read-etexts +exec = sugar-activity ReadEtextsActivity3.ReadEtextsActivity +show_launcher = no +mime_types = text/plain;application/zip +activity_version = 1 +license = GPLv2+ diff --git a/examples/JS_examples/Making_Shared_Activities/activity/activity.info~ b/examples/JS_examples/Making_Shared_Activities/activity/activity.info~ new file mode 100644 index 0000000..137333f --- /dev/null +++ b/examples/JS_examples/Making_Shared_Activities/activity/activity.info~ @@ -0,0 +1,9 @@ +[Activity] +name = ReadEtexts II +service_name = net.flossmanuals.ReadETexts +icon = read-etexts +exec = sugar-activity ReadEtextsActivity4.ReadEtextsActivity +show_launcher = no +mime_types = text/plain;application/zip +activity_version = 1 +license = GPLv2+ diff --git a/examples/JS_examples/Making_Shared_Activities/activity/read-etexts.svg b/examples/JS_examples/Making_Shared_Activities/activity/read-etexts.svg new file mode 100644 index 0000000..5682ec8 --- /dev/null +++ b/examples/JS_examples/Making_Shared_Activities/activity/read-etexts.svg @@ -0,0 +1,71 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#000000"> + <!ENTITY fill_color "#FFFFFF"> +]><svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="48px" + height="48px" + id="svg116" + sodipodi:version="0.32" + inkscape:version="0.46+devel" + sodipodi:docname="New document 4" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs118"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="48 : 24 : 1" + inkscape:persp3d-origin="24 : 16 : 1" + id="perspective124" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="7" + inkscape:cx="24" + inkscape:cy="24" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:grid-bbox="true" + inkscape:document-units="px" + inkscape:window-width="1024" + inkscape:window-height="698" + inkscape:window-x="0" + inkscape:window-y="25" /> + <metadata + id="metadata121"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:label="Layer 1" + inkscape:groupmode="layer"> + <rect + style="fill:&fill_color;;stroke:&stroke_color;;stroke-opacity:1" + id="rect904" + width="36.142857" + height="32.142857" + x="4.1428571" + y="7.1428571" /> + </g> +</svg> diff --git a/examples/JS_examples/Making_Shared_Activities/po/ReadEtextsIII.pot b/examples/JS_examples/Making_Shared_Activities/po/ReadEtextsIII.pot new file mode 100644 index 0000000..fb2e8cb --- /dev/null +++ b/examples/JS_examples/Making_Shared_Activities/po/ReadEtextsIII.pot @@ -0,0 +1,61 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-03-05 10:32-0600\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: activity/activity.info:2 +msgid "ReadEtexts III" +msgstr "" + +#: toolbar.py:36 +msgid "Back" +msgstr "" + +#: toolbar.py:42 +msgid "Forward" +msgstr "" + +#: toolbar.py:117 +msgid "Zoom out" +msgstr "" + +#: toolbar.py:122 +msgid "Zoom in" +msgstr "" + +#: toolbar.py:132 +msgid "Fullscreen" +msgstr "" + +#: ReadEtextsActivity3.py:108 +msgid "Edit" +msgstr "" + +#: ReadEtextsActivity3.py:112 +msgid "Read" +msgstr "" + +#: ReadEtextsActivity3.py:120 +msgid "View" +msgstr "" + +#: ReadEtextsActivity3.py:541 +msgid "Failure" +msgstr "" + +#: ReadEtextsActivity3.py:541 +msgid "Error getting document from tube" +msgstr "" diff --git a/examples/JS_examples/Making_Shared_Activities/setup.py b/examples/JS_examples/Making_Shared_Activities/setup.py new file mode 100755 index 0000000..ebc201d --- /dev/null +++ b/examples/JS_examples/Making_Shared_Activities/setup.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +# setup.py + +# Copyright (C) 2006, Red Hat, Inc. +# +# 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 + +from sugar.activity import bundlebuilder + +bundlebuilder.start() diff --git a/examples/JS_examples/Making_Shared_Activities/toolbar.py b/examples/JS_examples/Making_Shared_Activities/toolbar.py new file mode 100644 index 0000000..564602c --- /dev/null +++ b/examples/JS_examples/Making_Shared_Activities/toolbar.py @@ -0,0 +1,138 @@ +# toolbar.,py The toolbars used by ReadEtextsActivity. +# +# Copyright (C) 2010, James Simmons. +# Adapted from code Copyright (C) Red Hat Inc. +# +# 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 + +from gettext import gettext as _ +import re + +import pango +import gobject +import gtk + +from sugar.graphics.toolbutton import ToolButton + +class ReadToolbar(gtk.Toolbar): + __gtype_name__ = 'ReadToolbar' + + def __init__(self): + gtk.Toolbar.__init__(self) + + self.back = ToolButton('go-previous') + self.back.set_tooltip(_('Back')) + self.back.props.sensitive = False + self.insert(self.back, -1) + self.back.show() + + self.forward = ToolButton('go-next') + self.forward.set_tooltip(_('Forward')) + self.forward.props.sensitive = False + self.insert(self.forward, -1) + self.forward.show() + + num_page_item = gtk.ToolItem() + + self.num_page_entry = gtk.Entry() + self.num_page_entry.set_text('0') + self.num_page_entry.set_alignment(1) + self.num_page_entry.connect('insert-text', + self.num_page_entry_insert_text_cb) + + self.num_page_entry.set_width_chars(4) + + num_page_item.add(self.num_page_entry) + self.num_page_entry.show() + + self.insert(num_page_item, -1) + num_page_item.show() + + total_page_item = gtk.ToolItem() + + self.total_page_label = gtk.Label() + + label_attributes = pango.AttrList() + label_attributes.insert(pango.AttrSize(14000, 0, -1)) + label_attributes.insert(pango.AttrForeground(65535, 65535, 65535, 0, -1)) + self.total_page_label.set_attributes(label_attributes) + + self.total_page_label.set_text(' / 0') + total_page_item.add(self.total_page_label) + self.total_page_label.show() + + self.insert(total_page_item, -1) + total_page_item.show() + + def num_page_entry_insert_text_cb(self, entry, text, length, position): + if not re.match('[0-9]', text): + entry.emit_stop_by_name('insert-text') + return True + return False + + def update_nav_buttons(self): + current_page = self.current_page + self.back.props.sensitive = current_page > 0 + self.forward.props.sensitive = \ + current_page < self.total_pages - 1 + + self.num_page_entry.props.text = str(current_page + 1) + self.total_page_label.props.label = \ + ' / ' + str(self.total_pages) + + def set_total_pages(self, pages): + self.total_pages = pages + + def set_current_page(self, page): + self.current_page = page + self.update_nav_buttons() + +class ViewToolbar(gtk.Toolbar): + __gtype_name__ = 'ViewToolbar' + + __gsignals__ = { + 'needs-update-size': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])), + 'go-fullscreen': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])) + } + + def __init__(self): + gtk.Toolbar.__init__(self) + self.zoom_out = ToolButton('zoom-out') + self.zoom_out.set_tooltip(_('Zoom out')) + self.insert(self.zoom_out, -1) + self.zoom_out.show() + + self.zoom_in = ToolButton('zoom-in') + self.zoom_in.set_tooltip(_('Zoom in')) + self.insert(self.zoom_in, -1) + self.zoom_in.show() + + spacer = gtk.SeparatorToolItem() + spacer.props.draw = False + self.insert(spacer, -1) + spacer.show() + + self.fullscreen = ToolButton('view-fullscreen') + self.fullscreen.set_tooltip(_('Fullscreen')) + self.fullscreen.connect('clicked', self.fullscreen_cb) + self.insert(self.fullscreen, -1) + self.fullscreen.show() + + def fullscreen_cb(self, button): + self.emit('go-fullscreen') diff --git a/examples/JS_examples/MiniChat/MANIFEST b/examples/JS_examples/MiniChat/MANIFEST new file mode 100644 index 0000000..51c6abf --- /dev/null +++ b/examples/JS_examples/MiniChat/MANIFEST @@ -0,0 +1,10 @@ + +setup.py + + + +activity/activity.info +minichat.py +textchannel.py +activity/chat.svg +po/MiniChat.pot diff --git a/examples/JS_examples/MiniChat/activity/activity.info b/examples/JS_examples/MiniChat/activity/activity.info new file mode 100644 index 0000000..689414c --- /dev/null +++ b/examples/JS_examples/MiniChat/activity/activity.info @@ -0,0 +1,9 @@ +[Activity] +name = Mini Chat +service_name = net.flossmanuals.MiniChat +icon = chat +exec = sugar-activity minichat.MiniChat +show_launcher = yes +activity_version = 1 +license = GPLv2+ + diff --git a/examples/JS_examples/MiniChat/activity/activity.info~ b/examples/JS_examples/MiniChat/activity/activity.info~ new file mode 100644 index 0000000..8355763 --- /dev/null +++ b/examples/JS_examples/MiniChat/activity/activity.info~ @@ -0,0 +1,9 @@ +[Activity] +name = MiniChat +service_name = net.flossmanuals.MiniChat +icon = chat +exec = sugar-activity ReadEtextsActivity4.ReadEtextsActivity +show_launcher = no +mime_types = text/plain;application/zip +activity_version = 1 +license = GPLv2+ diff --git a/examples/JS_examples/MiniChat/activity/chat.svg b/examples/JS_examples/MiniChat/activity/chat.svg new file mode 100644 index 0000000..b0df51b --- /dev/null +++ b/examples/JS_examples/MiniChat/activity/chat.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#010101"> + <!ENTITY fill_color "#FFFFFF"> +]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="activity-chat"> + <path d="M9.263,48.396c0.682,1.152,6.027,0.059,8.246-1.463 c2.102-1.432,3.207-2.596,4.336-2.596c1.133,0,12.54,0.92,20.935-5.715c7.225-5.707,9.773-13.788,4.52-21.437 c-5.252-7.644-13.832-9.08-20.878-8.56C16.806,9.342,4.224,16.91,4.677,28.313c0.264,6.711,3.357,9.143,4.922,10.703 c1.562,1.566,4.545,1.566,2.992,5.588C11.981,46.183,8.753,47.522,9.263,48.396z" display="inline" fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5"/> +</g></svg><!-- " --> diff --git a/examples/JS_examples/MiniChat/minichat.py b/examples/JS_examples/MiniChat/minichat.py new file mode 100644 index 0000000..ce48cea --- /dev/null +++ b/examples/JS_examples/MiniChat/minichat.py @@ -0,0 +1,325 @@ +# minichat.py +# Copyright 2007-2008 One Laptop Per Child +# +# 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 + +from gettext import gettext as _ +import hippo +import gtk +import pango +import logging +from sugar.activity.activity import Activity, ActivityToolbox, SCOPE_PRIVATE +from sugar.graphics.alert import NotifyAlert +from sugar.graphics.style import (Color, COLOR_BLACK, COLOR_WHITE, + COLOR_BUTTON_GREY, FONT_BOLD, FONT_NORMAL) +from sugar.graphics.roundbox import CanvasRoundBox +from sugar.graphics.xocolor import XoColor +from sugar.graphics.palette import Palette, CanvasInvoker + +from textchannel import TextChannelWrapper + +logger = logging.getLogger('minichat-activity') + +class MiniChat(Activity): + def __init__(self, handle): + Activity.__init__(self, handle) + + root = self.make_root() + self.set_canvas(root) + root.show_all() + self.entry.grab_focus() + + toolbox = ActivityToolbox(self) + activity_toolbar = toolbox.get_activity_toolbar() + activity_toolbar.keep.props.visible = False + self.set_toolbox(toolbox) + toolbox.show() + + self.owner = self._pservice.get_owner() + # Auto vs manual scrolling: + self._scroll_auto = True + self._scroll_value = 0.0 + # Track last message, to combine several messages: + self._last_msg = None + self._last_msg_sender = None + self.text_channel = None + + if self._shared_activity: + # we are joining the activity + self.connect('joined', self._joined_cb) + if self.get_shared(): + # we have already joined + self._joined_cb() + else: + # we are creating the activity + if not self.metadata or self.metadata.get('share-scope', + SCOPE_PRIVATE) == SCOPE_PRIVATE: + # if we are in private session + self._alert(_('Off-line'), _('Share, or invite someone.')) + self.connect('shared', self._shared_cb) + + def _shared_cb(self, activity): + logger.debug('Chat was shared') + self._setup() + + def _joined_cb(self, activity): + """Joined a shared activity.""" + if not self._shared_activity: + return + logger.debug('Joined a shared chat') + for buddy in self._shared_activity.get_joined_buddies(): + self._buddy_already_exists(buddy) + self._setup() + + def _setup(self): + self.text_channel = TextChannelWrapper( + self._shared_activity.telepathy_text_chan, + self._shared_activity.telepathy_conn) + self.text_channel.set_received_callback(self._received_cb) + self._alert(_('On-line'), _('Connected')) + self._shared_activity.connect('buddy-joined', self._buddy_joined_cb) + self._shared_activity.connect('buddy-left', self._buddy_left_cb) + self.entry.set_sensitive(True) + self.entry.grab_focus() + + def _received_cb(self, buddy, text): + """Show message that was received.""" + if buddy: + nick = buddy.props.nick + else: + nick = '???' + logger.debug('Received message from %s: %s', nick, text) + self.add_text(buddy, text) + + def _alert(self, title, text=None): + alert = NotifyAlert(timeout=5) + alert.props.title = title + alert.props.msg = text + self.add_alert(alert) + alert.connect('response', self._alert_cancel_cb) + alert.show() + + def _alert_cancel_cb(self, alert, response_id): + self.remove_alert(alert) + + def _buddy_joined_cb (self, activity, buddy): + """Show a buddy who joined""" + if buddy == self.owner: + return + if buddy: + nick = buddy.props.nick + else: + nick = '???' + self.add_text(buddy, buddy.props.nick+' '+_('joined the chat'), + status_message=True) + + def _buddy_left_cb (self, activity, buddy): + """Show a buddy who joined""" + if buddy == self.owner: + return + if buddy: + nick = buddy.props.nick + else: + nick = '???' + self.add_text(buddy, buddy.props.nick+' '+_('left the chat'), + status_message=True) + + def _buddy_already_exists(self, buddy): + """Show a buddy already in the chat.""" + if buddy == self.owner: + return + if buddy: + nick = buddy.props.nick + else: + nick = '???' + self.add_text(buddy, buddy.props.nick+' '+_('is here'), + status_message=True) + + def make_root(self): + conversation = hippo.CanvasBox( + spacing=0, + background_color=COLOR_WHITE.get_int()) + self.conversation = conversation + + entry = gtk.Entry() + entry.modify_bg(gtk.STATE_INSENSITIVE, + COLOR_WHITE.get_gdk_color()) + entry.modify_base(gtk.STATE_INSENSITIVE, + COLOR_WHITE.get_gdk_color()) + entry.set_sensitive(False) + entry.connect('activate', self.entry_activate_cb) + self.entry = entry + + hbox = gtk.HBox() + hbox.add(entry) + + sw = hippo.CanvasScrollbars() + sw.set_policy(hippo.ORIENTATION_HORIZONTAL, hippo.SCROLLBAR_NEVER) + sw.set_root(conversation) + self.scrolled_window = sw + + vadj = self.scrolled_window.props.widget.get_vadjustment() + vadj.connect('changed', self.rescroll) + vadj.connect('value-changed', self.scroll_value_changed_cb) + + canvas = hippo.Canvas() + canvas.set_root(sw) + + box = gtk.VBox(homogeneous=False) + box.pack_start(hbox, expand=False) + box.pack_start(canvas) + + return box + + def rescroll(self, adj, scroll=None): + """Scroll the chat window to the bottom""" + if self._scroll_auto: + adj.set_value(adj.upper-adj.page_size) + self._scroll_value = adj.get_value() + + def scroll_value_changed_cb(self, adj, scroll=None): + """Turn auto scrolling on or off. + + If the user scrolled up, turn it off. + If the user scrolled to the bottom, turn it back on. + """ + if adj.get_value() < self._scroll_value: + self._scroll_auto = False + elif adj.get_value() == adj.upper-adj.page_size: + self._scroll_auto = True + + def add_text(self, buddy, text, status_message=False): + """Display text on screen, with name and colors. + + buddy -- buddy object + text -- string, what the buddy said + status_message -- boolean + False: show what buddy said + True: show what buddy did + + hippo layout: + .------------- rb ---------------. + | +name_vbox+ +----msg_vbox----+ | + | | | | | | + | | nick: | | +--msg_hbox--+ | | + | | | | | text | | | + | +---------+ | +------------+ | | + | | | | + | | +--msg_hbox--+ | | + | | | text | | | + | | +------------+ | | + | +----------------+ | + `--------------------------------' + """ + if buddy: + nick = buddy.props.nick + color = buddy.props.color + try: + color_stroke_html, color_fill_html = color.split(',') + except ValueError: + color_stroke_html, color_fill_html = ('#000000', '#888888') + # Select text color based on fill color: + 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 = Color(color_stroke_html).get_int() + color_fill = Color(color_fill_html).get_int() + if color_fill_gray < 0.5: + text_color = COLOR_WHITE.get_int() + else: + text_color = COLOR_BLACK.get_int() + else: + nick = '???' # XXX: should be '' but leave for debugging + color_stroke = COLOR_BLACK.get_int() + color_fill = COLOR_WHITE.get_int() + text_color = COLOR_BLACK.get_int() + color = '#000000,#FFFFFF' + + # Check for Right-To-Left languages: + if pango.find_base_dir(nick, -1) == pango.DIRECTION_RTL: + lang_rtl = True + else: + lang_rtl = False + + # Check if new message box or add text to previous: + new_msg = True + if self._last_msg_sender: + if not status_message: + if buddy == self._last_msg_sender: + # Add text to previous message + new_msg = False + + if not new_msg: + rb = self._last_msg + msg_vbox = rb.get_children()[1] + msg_hbox = hippo.CanvasBox( + orientation=hippo.ORIENTATION_HORIZONTAL) + msg_vbox.append(msg_hbox) + else: + 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 = hippo.CanvasText(text=nick+': ', + color=text_color, + font_desc=FONT_BOLD.get_pango_desc()) + 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 + + if text: + message = hippo.CanvasText( + text=text, + size_mode=hippo.CANVAS_SIZE_WRAP_WORD, + color=text_color, + font_desc=FONT_NORMAL.get_pango_desc(), + 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 entry_activate_cb(self, entry): + text = entry.props.text + logger.debug('Entry: %s' % text) + if text: + self.add_text(self.owner, text) + entry.props.text = '' + if self.text_channel: + self.text_channel.send(text) + else: + logger.debug('Tried to send message but text channel ' + 'not connected.') diff --git a/examples/JS_examples/MiniChat/po/MiniChat.pot b/examples/JS_examples/MiniChat/po/MiniChat.pot new file mode 100644 index 0000000..074f630 --- /dev/null +++ b/examples/JS_examples/MiniChat/po/MiniChat.pot @@ -0,0 +1,49 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-03-05 10:33-0600\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: activity/activity.info:2 +msgid "Mini Chat" +msgstr "" + +#: minichat.py:70 +msgid "Off-line" +msgstr "" + +#: minichat.py:70 +msgid "Share, or invite someone." +msgstr "" + +#: minichat.py:91 +msgid "On-line" +msgstr "" + +#: minichat.py:91 +msgid "Connected" +msgstr "" + +#: minichat.py:125 +msgid "joined the chat" +msgstr "" + +#: minichat.py:136 +msgid "left the chat" +msgstr "" + +#: minichat.py:147 +msgid "is here" +msgstr "" diff --git a/examples/JS_examples/MiniChat/setup.py b/examples/JS_examples/MiniChat/setup.py new file mode 100755 index 0000000..ebc201d --- /dev/null +++ b/examples/JS_examples/MiniChat/setup.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +# setup.py + +# Copyright (C) 2006, Red Hat, Inc. +# +# 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 + +from sugar.activity import bundlebuilder + +bundlebuilder.start() diff --git a/examples/JS_examples/MiniChat/textchannel.py b/examples/JS_examples/MiniChat/textchannel.py new file mode 100644 index 0000000..1dfe1ac --- /dev/null +++ b/examples/JS_examples/MiniChat/textchannel.py @@ -0,0 +1,137 @@ +# textchannel.py + +# Copyright 2007-2008 One Laptop Per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging + +from telepathy.client import Connection, Channel +from telepathy.interfaces import ( + CHANNEL_INTERFACE, CHANNEL_INTERFACE_GROUP, CHANNEL_TYPE_TEXT, + CONN_INTERFACE_ALIASING) +from telepathy.constants import ( + CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES, + CHANNEL_TEXT_MESSAGE_TYPE_NORMAL) + +class TextChannelWrapper(object): + """Wrap a telepathy Text Channel to make usage simpler.""" + def __init__(self, text_chan, conn): + """Connect to the text channel""" + self._activity_cb = None + self._activity_close_cb = None + self._text_chan = text_chan + self._conn = conn + self._logger = logging.getLogger( + 'minichat-activity.TextChannelWrapper') + self._signal_matches = [] + m = self._text_chan[CHANNEL_INTERFACE].connect_to_signal( + 'Closed', self._closed_cb) + self._signal_matches.append(m) + + def send(self, text): + """Send text over the Telepathy text channel.""" + # XXX Implement CHANNEL_TEXT_MESSAGE_TYPE_ACTION + if self._text_chan is not None: + self._text_chan[CHANNEL_TYPE_TEXT].Send( + CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, text) + + def close(self): + """Close the text channel.""" + self._logger.debug('Closing text channel') + try: + self._text_chan[CHANNEL_INTERFACE].Close() + except: + self._logger.debug('Channel disappeared!') + self._closed_cb() + + def _closed_cb(self): + """Clean up text channel.""" + self._logger.debug('Text channel closed.') + for match in self._signal_matches: + match.remove() + self._signal_matches = [] + self._text_chan = None + if self._activity_close_cb is not None: + self._activity_close_cb() + + def set_received_callback(self, callback): + """Connect the function callback to the signal. + + callback -- callback function taking buddy and text args + """ + if self._text_chan is None: + return + self._activity_cb = callback + m = self._text_chan[CHANNEL_TYPE_TEXT].connect_to_signal('Received', + self._received_cb) + self._signal_matches.append(m) + + def handle_pending_messages(self): + """Get pending messages and show them as received.""" + for id, timestamp, sender, type, flags, text in \ + self._text_chan[ + CHANNEL_TYPE_TEXT].ListPendingMessages(False): + self._received_cb(id, timestamp, sender, type, flags, text) + + def _received_cb(self, id, timestamp, sender, type, flags, text): + """Handle received text from the text channel. + + Converts sender to a Buddy. + Calls self._activity_cb which is a callback to the activity. + """ + if self._activity_cb: + buddy = self._get_buddy(sender) + self._activity_cb(buddy, text) + self._text_chan[ + CHANNEL_TYPE_TEXT].AcknowledgePendingMessages([id]) + else: + self._logger.debug('Throwing received message on the floor' + ' since there is no callback connected. See ' + 'set_received_callback') + + def set_closed_callback(self, callback): + """Connect a callback for when the text channel is closed. + + callback -- callback function taking no args + + """ + self._activity_close_cb = callback + + def _get_buddy(self, cs_handle): + """Get a Buddy from a (possibly channel-specific) handle.""" + # XXX This will be made redundant once Presence Service + # provides buddy resolution + from sugar.presence import presenceservice + # Get the Presence Service + pservice = presenceservice.get_instance() + # Get the Telepathy Connection + tp_name, tp_path = pservice.get_preferred_connection() + conn = Connection(tp_name, tp_path) + group = self._text_chan[CHANNEL_INTERFACE_GROUP] + my_csh = group.GetSelfHandle() + if my_csh == cs_handle: + handle = conn.GetSelfHandle() + elif group.GetGroupFlags() & \ + CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES: + handle = group.GetHandleOwners([cs_handle])[0] + else: + handle = cs_handle + + # XXX: deal with failure to get the handle owner + assert handle != 0 + + return pservice.get_buddy_by_telepathy_handle( + tp_name, tp_path, handle) diff --git a/examples/JS_examples/New_Style_Toolbars/MANIFEST b/examples/JS_examples/New_Style_Toolbars/MANIFEST new file mode 100644 index 0000000..f45e662 --- /dev/null +++ b/examples/JS_examples/New_Style_Toolbars/MANIFEST @@ -0,0 +1,9 @@ +toolbar.py +setup.py + + +activity/read-etexts.svg +activity/activity.info +ReadEtextsActivity4.py +mybutton.py +po/ReadEtextsIV.pot diff --git a/examples/JS_examples/New_Style_Toolbars/ReadEtextsActivity4.py b/examples/JS_examples/New_Style_Toolbars/ReadEtextsActivity4.py new file mode 100644 index 0000000..ad5604a --- /dev/null +++ b/examples/JS_examples/New_Style_Toolbars/ReadEtextsActivity4.py @@ -0,0 +1,805 @@ +# ReadEtextsActivity4.py A version of ReadEtextsActivity that supports +# sharing ebooks over a Stream Tube in Telepathy and has both new and +# old style toolbars. + +# Copyright (C) 2010 James D. Simmons +# +# 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 US + +import os +import re +import logging +import time +import zipfile +import gtk +import pango +import dbus +import gobject +import telepathy +from sugar.activity import activity + +from sugar.graphics.toolbutton import ToolButton + +_NEW_TOOLBAR_SUPPORT = True +try: + from sugar.graphics.toolbarbox import ToolbarBox + from sugar.graphics.toolbarbox import ToolbarButton + from sugar.activity.widgets import StopButton + from toolbar import ViewToolbar + from mybutton import MyActivityToolbarButton +except: + _NEW_TOOLBAR_SUPPORT = False + from toolbar import ReadToolbar, ViewToolbar + +from sugar.graphics.toggletoolbutton import ToggleToolButton +from sugar.graphics.menuitem import MenuItem + +from sugar.graphics import style +from sugar import network +from sugar.datastore import datastore +from sugar.graphics.alert import NotifyAlert +from gettext import gettext as _ + +page=0 +PAGE_SIZE = 45 +TOOLBAR_READ = 2 + +logger = logging.getLogger('read-etexts2-activity') + +class ReadHTTPRequestHandler(network.ChunkedGlibHTTPRequestHandler): + """HTTP Request Handler for transferring document while collaborating. + + RequestHandler class that integrates with Glib mainloop. It writes + the specified file to the client in chunks, returning control to the + mainloop between chunks. + + """ + def translate_path(self, path): + """Return the filepath to the shared document.""" + return self.server.filepath + + +class ReadHTTPServer(network.GlibTCPServer): + """HTTP Server for transferring document while collaborating.""" + def __init__(self, server_address, filepath): + """Set up the GlibTCPServer with the ReadHTTPRequestHandler. + + filepath -- path to shared document to be served. + """ + self.filepath = filepath + network.GlibTCPServer.__init__(self, server_address, + ReadHTTPRequestHandler) + + +class ReadURLDownloader(network.GlibURLDownloader): + """URLDownloader that provides content-length and content-type.""" + + def get_content_length(self): + """Return the content-length of the download.""" + if self._info is not None: + return int(self._info.headers.get('Content-Length')) + + def get_content_type(self): + """Return the content-type of the download.""" + if self._info is not None: + return self._info.headers.get('Content-type') + return None + +READ_STREAM_SERVICE = 'read-etexts-activity-http' + +class ReadEtextsActivity(activity.Activity): + def __init__(self, handle): + "The entry point to the Activity" + global page + activity.Activity.__init__(self, handle) + + self.fileserver = None + self.object_id = handle.object_id + + if _NEW_TOOLBAR_SUPPORT: + self.create_new_toolbar() + else: + self.create_old_toolbar() + + self.scrolled_window = gtk.ScrolledWindow() + self.scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self.scrolled_window.props.shadow_type = gtk.SHADOW_NONE + + self.textview = gtk.TextView() + self.textview.set_editable(False) + self.textview.set_cursor_visible(False) + self.textview.set_left_margin(50) + self.textview.connect("key_press_event", self.keypress_cb) + + self.progressbar = gtk.ProgressBar() + self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT) + self.progressbar.set_fraction(0.0) + + self.scrolled_window.add(self.textview) + self.textview.show() + self.scrolled_window.show() + + vbox = gtk.VBox() + vbox.pack_start(self.progressbar, False, False, 10) + vbox.pack_start(self.scrolled_window) + self.set_canvas(vbox) + vbox.show() + + page = 0 + self.clipboard = gtk.Clipboard(display=gtk.gdk.display_get_default(), \ + selection="CLIPBOARD") + self.textview.grab_focus() + self.font_desc = pango.FontDescription("sans %d" % style.zoom(10)) + self.textview.modify_font(self.font_desc) + + buffer = self.textview.get_buffer() + self.markset_id = buffer.connect("mark-set", self.mark_set_cb) + + self.unused_download_tubes = set() + self.want_document = True + self.download_content_length = 0 + self.download_content_type = None + # Status of temp file used for write_file: + self.tempfile = None + self.close_requested = False + self.connect("shared", self.shared_cb) + + self.is_received_document = False + + if self._shared_activity and handle.object_id == None: + # We're joining, and we don't already have the document. + if self.get_shared(): + # Already joined for some reason, just get the document + self.joined_cb(self) + else: + # Wait for a successful join before trying to get the document + self.connect("joined", self.joined_cb) + + def create_old_toolbar(self): + toolbox = activity.ActivityToolbox(self) + activity_toolbar = toolbox.get_activity_toolbar() + activity_toolbar.keep.props.visible = False + + self.edit_toolbar = activity.EditToolbar() + self.edit_toolbar.undo.props.visible = False + self.edit_toolbar.redo.props.visible = False + self.edit_toolbar.separator.props.visible = False + self.edit_toolbar.copy.set_sensitive(False) + self.edit_toolbar.copy.connect('clicked', self.edit_toolbar_copy_cb) + self.edit_toolbar.paste.props.visible = False + toolbox.add_toolbar(_('Edit'), self.edit_toolbar) + self.edit_toolbar.show() + + self.read_toolbar = ReadToolbar() + toolbox.add_toolbar(_('Read'), self.read_toolbar) + self.read_toolbar.back.connect('clicked', self.go_back_cb) + self.read_toolbar.forward.connect('clicked', self.go_forward_cb) + self.read_toolbar.num_page_entry.connect('activate', \ + self.num_page_entry_activate_cb) + self.read_toolbar.show() + + self.view_toolbar = ViewToolbar() + toolbox.add_toolbar(_('View'), self.view_toolbar) + self.view_toolbar.connect('go-fullscreen', \ + self.view_toolbar_go_fullscreen_cb) + self.view_toolbar.zoom_in.connect('clicked', self.zoom_in_cb) + self.view_toolbar.zoom_out.connect('clicked', self.zoom_out_cb) + self.view_toolbar.show() + + self.set_toolbox(toolbox) + toolbox.show() + self.toolbox.set_current_toolbar(TOOLBAR_READ) + + def create_new_toolbar(self): + toolbar_box = ToolbarBox() + + activity_button = MyActivityToolbarButton(self) + toolbar_box.toolbar.insert(activity_button, 0) + activity_button.show() + + self.edit_toolbar = activity.EditToolbar() + self.edit_toolbar.undo.props.visible = False + self.edit_toolbar.redo.props.visible = False + self.edit_toolbar.separator.props.visible = False + self.edit_toolbar.copy.set_sensitive(False) + self.edit_toolbar.copy.connect('clicked', self.edit_toolbar_copy_cb) + self.edit_toolbar.paste.props.visible = False + + edit_toolbar_button = ToolbarButton( + page=self.edit_toolbar, + icon_name='toolbar-edit') + self.edit_toolbar.show() + toolbar_box.toolbar.insert(edit_toolbar_button, -1) + edit_toolbar_button.show() + + self.view_toolbar = ViewToolbar() + self.view_toolbar.connect('go-fullscreen', \ + self.view_toolbar_go_fullscreen_cb) + self.view_toolbar.zoom_in.connect('clicked', self.zoom_in_cb) + self.view_toolbar.zoom_out.connect('clicked', self.zoom_out_cb) + self.view_toolbar.show() + view_toolbar_button = ToolbarButton( + page=self.view_toolbar, + icon_name='toolbar-view') + toolbar_box.toolbar.insert(view_toolbar_button, -1) + view_toolbar_button.show() + + self.back = ToolButton('go-previous') + self.back.set_tooltip(_('Back')) + self.back.props.sensitive = False + self.back.connect('clicked', self.go_back_cb) + toolbar_box.toolbar.insert(self.back, -1) + self.back.show() + + self.forward = ToolButton('go-next') + self.forward.set_tooltip(_('Forward')) + self.forward.props.sensitive = False + self.forward.connect('clicked', self.go_forward_cb) + toolbar_box.toolbar.insert(self.forward, -1) + self.forward.show() + + num_page_item = gtk.ToolItem() + self.num_page_entry = gtk.Entry() + self.num_page_entry.set_text('0') + self.num_page_entry.set_alignment(1) + self.num_page_entry.connect('insert-text', + self.__new_num_page_entry_insert_text_cb) + self.num_page_entry.connect('activate', + self.__new_num_page_entry_activate_cb) + self.num_page_entry.set_width_chars(4) + num_page_item.add(self.num_page_entry) + self.num_page_entry.show() + toolbar_box.toolbar.insert(num_page_item, -1) + num_page_item.show() + + total_page_item = gtk.ToolItem() + self.total_page_label = gtk.Label() + + label_attributes = pango.AttrList() + label_attributes.insert(pango.AttrSize(14000, 0, -1)) + label_attributes.insert(pango.AttrForeground(65535, 65535, + 65535, 0, -1)) + self.total_page_label.set_attributes(label_attributes) + + self.total_page_label.set_text(' / 0') + total_page_item.add(self.total_page_label) + self.total_page_label.show() + toolbar_box.toolbar.insert(total_page_item, -1) + total_page_item.show() + + separator = gtk.SeparatorToolItem() + separator.props.draw = False + separator.set_expand(True) + toolbar_box.toolbar.insert(separator, -1) + separator.show() + + stop_button = StopButton(self) + stop_button.props.accelerator = '<Ctrl><Shift>Q' + toolbar_box.toolbar.insert(stop_button, -1) + stop_button.show() + + self.set_toolbar_box(toolbar_box) + toolbar_box.show() + + def __new_num_page_entry_insert_text_cb(self, entry, text, length, position): + if not re.match('[0-9]', text): + entry.emit_stop_by_name('insert-text') + return True + return False + + def __new_num_page_entry_activate_cb(self, entry): + global page + if entry.props.text: + new_page = int(entry.props.text) - 1 + else: + new_page = 0 + + if new_page >= self.total_pages: + new_page = self.total_pages - 1 + elif new_page < 0: + new_page = 0 + + self.current_page = new_page + self.set_current_page(new_page) + self.show_page(new_page) + entry.props.text = str(new_page + 1) + self.update_nav_buttons() + page = new_page + + def update_nav_buttons(self): + current_page = self.current_page + self.back.props.sensitive = current_page > 0 + self.forward.props.sensitive = \ + current_page < self.total_pages - 1 + + self.num_page_entry.props.text = str(current_page + 1) + self.total_page_label.props.label = \ + ' / ' + str(self.total_pages) + + def set_total_pages(self, pages): + self.total_pages = pages + + def set_current_page(self, page): + self.current_page = page + self.update_nav_buttons() + + def keypress_cb(self, widget, event): + "Respond when the user presses one of the arrow keys" + keyname = gtk.gdk.keyval_name(event.keyval) + print keyname + if keyname == 'plus': + self.font_increase() + return True + if keyname == 'minus': + self.font_decrease() + return True + if keyname == 'Page_Up' : + self.page_previous() + return True + if keyname == 'Page_Down': + self.page_next() + return True + if keyname == 'Up' or keyname == 'KP_Up' \ + or keyname == 'KP_Left': + self.scroll_up() + return True + if keyname == 'Down' or keyname == 'KP_Down' \ + or keyname == 'KP_Right': + self.scroll_down() + return True + return False + + def num_page_entry_activate_cb(self, entry): + global page + if entry.props.text: + new_page = int(entry.props.text) - 1 + else: + new_page = 0 + + if new_page >= self.read_toolbar.total_pages: + new_page = self.read_toolbar.total_pages - 1 + elif new_page < 0: + new_page = 0 + + self.read_toolbar.current_page = new_page + self.read_toolbar.set_current_page(new_page) + self.show_page(new_page) + entry.props.text = str(new_page + 1) + self.read_toolbar.update_nav_buttons() + page = new_page + + def go_back_cb(self, button): + self.page_previous() + + def go_forward_cb(self, button): + self.page_next() + + def page_previous(self): + global page + page=page-1 + if page < 0: page=0 + if _NEW_TOOLBAR_SUPPORT: + self.set_current_page(page) + else: + self.read_toolbar.set_current_page(page) + self.show_page(page) + v_adjustment = self.scrolled_window.get_vadjustment() + v_adjustment.value = v_adjustment.upper - v_adjustment.page_size + + def page_next(self): + global page + page=page+1 + if page >= len(self.page_index): page=0 + if _NEW_TOOLBAR_SUPPORT: + self.set_current_page(page) + else: + self.read_toolbar.set_current_page(page) + self.show_page(page) + v_adjustment = self.scrolled_window.get_vadjustment() + v_adjustment.value = v_adjustment.lower + + def zoom_in_cb(self, button): + self.font_increase() + + def zoom_out_cb(self, button): + self.font_decrease() + + def font_decrease(self): + font_size = self.font_desc.get_size() / 1024 + font_size = font_size - 1 + if font_size < 1: + font_size = 1 + self.font_desc.set_size(font_size * 1024) + self.textview.modify_font(self.font_desc) + + def font_increase(self): + font_size = self.font_desc.get_size() / 1024 + font_size = font_size + 1 + self.font_desc.set_size(font_size * 1024) + self.textview.modify_font(self.font_desc) + + def mark_set_cb(self, textbuffer, iter, textmark): + + if textbuffer.get_has_selection(): + begin, end = textbuffer.get_selection_bounds() + self.edit_toolbar.copy.set_sensitive(True) + else: + self.edit_toolbar.copy.set_sensitive(False) + + def edit_toolbar_copy_cb(self, button): + textbuffer = self.textview.get_buffer() + begin, end = textbuffer.get_selection_bounds() + copy_text = textbuffer.get_text(begin, end) + self.clipboard.set_text(copy_text) + + def view_toolbar_go_fullscreen_cb(self, view_toolbar): + self.fullscreen() + + def scroll_down(self): + v_adjustment = self.scrolled_window.get_vadjustment() + if v_adjustment.value == v_adjustment.upper - \ + v_adjustment.page_size: + self.page_next() + return + if v_adjustment.value < v_adjustment.upper - \ + v_adjustment.page_size: + new_value = v_adjustment.value + v_adjustment.step_increment + if new_value > v_adjustment.upper - v_adjustment.page_size: + new_value = v_adjustment.upper - v_adjustment.page_size + v_adjustment.value = new_value + + def scroll_up(self): + v_adjustment = self.scrolled_window.get_vadjustment() + if v_adjustment.value == v_adjustment.lower: + self.page_previous() + return + if v_adjustment.value > v_adjustment.lower: + new_value = v_adjustment.value - \ + v_adjustment.step_increment + if new_value < v_adjustment.lower: + new_value = v_adjustment.lower + v_adjustment.value = new_value + + def show_page(self, page_number): + global PAGE_SIZE, current_word + position = self.page_index[page_number] + self.etext_file.seek(position) + linecount = 0 + label_text = '\n\n\n' + textbuffer = self.textview.get_buffer() + while linecount < PAGE_SIZE: + line = self.etext_file.readline() + label_text = label_text + unicode(line, 'iso-8859-1') + linecount = linecount + 1 + label_text = label_text + '\n\n\n' + textbuffer.set_text(label_text) + self.textview.set_buffer(textbuffer) + + def save_extracted_file(self, zipfile, filename): + "Extract the file to a temp directory for viewing" + filebytes = zipfile.read(filename) + outfn = self.make_new_filename(filename) + if (outfn == ''): + return False + f = open(os.path.join(self.get_activity_root(), 'tmp', outfn), 'w') + try: + f.write(filebytes) + finally: + f.close() + + def get_saved_page_number(self): + global page + title = self.metadata.get('title', '') + if title == '' or not title[len(title)- 1].isdigit(): + page = 0 + else: + i = len(title) - 1 + newPage = '' + while (title[i].isdigit() and i > 0): + newPage = title[i] + newPage + i = i - 1 + if title[i] == 'P': + page = int(newPage) - 1 + else: + # not a page number; maybe a volume number. + page = 0 + + def save_page_number(self): + global page + title = self.metadata.get('title', '') + if title == '' or not title[len(title)- 1].isdigit(): + title = title + ' P' + str(page + 1) + else: + i = len(title) - 1 + while (title[i].isdigit() and i > 0): + i = i - 1 + if title[i] == 'P': + title = title[0:i] + 'P' + str(page + 1) + else: + title = title + ' P' + str(page + 1) + self.metadata['title'] = title + + def read_file(self, filename): + "Read the Etext file" + global PAGE_SIZE, page + + tempfile = os.path.join(self.get_activity_root(), 'instance', \ + 'tmp%i' % time.time()) + os.link(filename, tempfile) + self.tempfile = tempfile + + if zipfile.is_zipfile(filename): + self.zf = zipfile.ZipFile(filename, 'r') + self.book_files = self.zf.namelist() + self.save_extracted_file(self.zf, self.book_files[0]) + currentFileName = os.path.join(self.get_activity_root(), \ + 'tmp', self.book_files[0]) + else: + currentFileName = filename + + self.etext_file = open(currentFileName,"r") + self.page_index = [ 0 ] + pagecount = 0 + linecount = 0 + while self.etext_file: + line = self.etext_file.readline() + if not line: + break + linecount = linecount + 1 + if linecount >= PAGE_SIZE: + position = self.etext_file.tell() + self.page_index.append(position) + linecount = 0 + pagecount = pagecount + 1 + if filename.endswith(".zip"): + os.remove(currentFileName) + self.get_saved_page_number() + self.show_page(page) + if _NEW_TOOLBAR_SUPPORT: + self.set_total_pages(pagecount + 1) + self.set_current_page(page) + else: + self.read_toolbar.set_total_pages(pagecount + 1) + self.read_toolbar.set_current_page(page) + + # We've got the document, so if we're a shared activity, offer it + if self.get_shared(): + self.watch_for_tubes() + self.share_document() + + def make_new_filename(self, filename): + partition_tuple = filename.rpartition('/') + return partition_tuple[2] + + def write_file(self, filename): + "Save meta data for the file." + if self.is_received_document: + # This document was given to us by someone, so we have + # to save it to the Journal. + self.etext_file.seek(0) + filebytes = self.etext_file.read() + print 'saving shared document' + f = open(filename, 'wb') + try: + f.write(filebytes) + finally: + f.close() + elif self.tempfile: + if self.close_requested: + os.link(self.tempfile, filename) + logger.debug("Removing temp file %s because we will close", \ + self.tempfile) + os.unlink(self.tempfile) + self.tempfile = None + else: + # skip saving empty file + raise NotImplementedError + + self.metadata['activity'] = self.get_bundle_id() + self.save_page_number() + + def can_close(self): + self.close_requested = True + return True + + def joined_cb(self, also_self): + """Callback for when a shared activity is joined. + + Get the shared document from another participant. + """ + self.watch_for_tubes() + gobject.idle_add(self.get_document) + + def get_document(self): + if not self.want_document: + return False + + # Assign a file path to download if one doesn't exist yet + if not self._jobject.file_path: + path = os.path.join(self.get_activity_root(), 'instance', + 'tmp%i' % time.time()) + else: + path = self._jobject.file_path + + # Pick an arbitrary tube we can try to download the document from + try: + tube_id = self.unused_download_tubes.pop() + except (ValueError, KeyError), e: + logger.debug('No tubes to get the document from right now: %s', + e) + return False + + # Avoid trying to download the document multiple times at once + self.want_document = False + gobject.idle_add(self.download_document, tube_id, path) + return False + + def download_document(self, tube_id, path): + chan = self._shared_activity.telepathy_tubes_chan + iface = chan[telepathy.CHANNEL_TYPE_TUBES] + addr = iface.AcceptStreamTube(tube_id, + telepathy.SOCKET_ADDRESS_TYPE_IPV4, + telepathy.SOCKET_ACCESS_CONTROL_LOCALHOST, 0, + utf8_strings=True) + logger.debug('Accepted stream tube: listening address is %r', \ + addr) + assert isinstance(addr, dbus.Struct) + assert len(addr) == 2 + assert isinstance(addr[0], str) + assert isinstance(addr[1], (int, long)) + assert addr[1] > 0 and addr[1] < 65536 + port = int(addr[1]) + + self.progressbar.show() + getter = ReadURLDownloader("http://%s:%d/document" + % (addr[0], port)) + getter.connect("finished", self.download_result_cb, tube_id) + getter.connect("progress", self.download_progress_cb, tube_id) + getter.connect("error", self.download_error_cb, tube_id) + logger.debug("Starting download to %s...", path) + getter.start(path) + self.download_content_length = getter.get_content_length() + self.download_content_type = getter.get_content_type() + return False + + def download_progress_cb(self, getter, bytes_downloaded, tube_id): + if self.download_content_length > 0: + logger.debug("Downloaded %u of %u bytes from tube %u...", + bytes_downloaded, self.download_content_length, + tube_id) + else: + logger.debug("Downloaded %u bytes from tube %u...", + bytes_downloaded, tube_id) + total = self.download_content_length + self.set_downloaded_bytes(bytes_downloaded, total) + gtk.gdk.threads_enter() + while gtk.events_pending(): + gtk.main_iteration() + gtk.gdk.threads_leave() + + def set_downloaded_bytes(self, bytes, total): + fraction = float(bytes) / float(total) + self.progressbar.set_fraction(fraction) + logger.debug("Downloaded percent", fraction) + + def clear_downloaded_bytes(self): + self.progressbar.set_fraction(0.0) + logger.debug("Cleared download bytes") + + def download_error_cb(self, getter, err, tube_id): + self.progressbar.hide() + logger.debug("Error getting document from tube %u: %s", + tube_id, err) + self.alert(_('Failure'), _('Error getting document from tube')) + self.want_document = True + self.download_content_length = 0 + self.download_content_type = None + gobject.idle_add(self.get_document) + + def download_result_cb(self, getter, tempfile, suggested_name, tube_id): + if self.download_content_type.startswith('text/html'): + # got an error page instead + self.download_error_cb(getter, 'HTTP Error', tube_id) + return + + del self.unused_download_tubes + + self.tempfile = tempfile + file_path = os.path.join(self.get_activity_root(), 'instance', + '%i' % time.time()) + logger.debug("Saving file %s to datastore...", file_path) + os.link(tempfile, file_path) + self._jobject.file_path = file_path + datastore.write(self._jobject, transfer_ownership=True) + + logger.debug("Got document %s (%s) from tube %u", + tempfile, suggested_name, tube_id) + self.is_received_document = True + self.read_file(tempfile) + self.save() + self.progressbar.hide() + + def shared_cb(self, activityid): + """Callback when activity shared. + + Set up to share the document. + + """ + # We initiated this activity and have now shared it, so by + # definition we have the file. + logger.debug('Activity became shared') + self.watch_for_tubes() + self.share_document() + + def share_document(self): + """Share the document.""" + h = hash(self._activity_id) + port = 1024 + (h % 64511) + logger.debug('Starting HTTP server on port %d', port) + self.fileserver = ReadHTTPServer(("", port), + self.tempfile) + + # Make a tube for it + chan = self._shared_activity.telepathy_tubes_chan + iface = chan[telepathy.CHANNEL_TYPE_TUBES] + self.fileserver_tube_id = iface.OfferStreamTube(READ_STREAM_SERVICE, + {}, + telepathy.SOCKET_ADDRESS_TYPE_IPV4, + ('127.0.0.1', dbus.UInt16(port)), + telepathy.SOCKET_ACCESS_CONTROL_LOCALHOST, 0) + + def watch_for_tubes(self): + """Watch for new tubes.""" + tubes_chan = self._shared_activity.telepathy_tubes_chan + + tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube', + self.new_tube_cb) + tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes( + reply_handler=self.list_tubes_reply_cb, + error_handler=self.list_tubes_error_cb) + + def new_tube_cb(self, tube_id, initiator, tube_type, service, params, + state): + """Callback when a new tube becomes available.""" + logger.debug('New tube: ID=%d initator=%d type=%d service=%s ' + 'params=%r state=%d', tube_id, initiator, tube_type, + service, params, state) + if service == READ_STREAM_SERVICE: + logger.debug('I could download from that tube') + self.unused_download_tubes.add(tube_id) + # if no download is in progress, let's fetch the document + if self.want_document: + gobject.idle_add(self.get_document) + + def list_tubes_reply_cb(self, tubes): + """Callback when new tubes are available.""" + for tube_info in tubes: + self.new_tube_cb(*tube_info) + + def list_tubes_error_cb(self, e): + """Handle ListTubes error by logging.""" + logger.error('ListTubes() failed: %s', e) + + def alert(self, title, text=None): + alert = NotifyAlert(timeout=20) + alert.props.title = title + alert.props.msg = text + self.add_alert(alert) + alert.connect('response', self.alert_cancel_cb) + alert.show() + + def alert_cancel_cb(self, alert, response_id): + self.remove_alert(alert) + self.textview.grab_focus() diff --git a/examples/JS_examples/New_Style_Toolbars/activity/activity.info b/examples/JS_examples/New_Style_Toolbars/activity/activity.info new file mode 100644 index 0000000..7197bd8 --- /dev/null +++ b/examples/JS_examples/New_Style_Toolbars/activity/activity.info @@ -0,0 +1,9 @@ +[Activity] +name = ReadEtexts IV +service_name = net.flossmanuals.ReadETextsActivity4 +icon = read-etexts +exec = sugar-activity ReadEtextsActivity4.ReadEtextsActivity +show_launcher = no +mime_types = text/plain;application/zip +activity_version = 1 +license = GPLv2+ diff --git a/examples/JS_examples/New_Style_Toolbars/activity/activity.info~ b/examples/JS_examples/New_Style_Toolbars/activity/activity.info~ new file mode 100644 index 0000000..670a054 --- /dev/null +++ b/examples/JS_examples/New_Style_Toolbars/activity/activity.info~ @@ -0,0 +1,9 @@ +[Activity] +name = ReadEtexts II +service_name = net.flossmanuals.ReadETextsActivity4 +icon = read-etexts +exec = sugar-activity ReadEtextsActivity4.ReadEtextsActivity +show_launcher = no +mime_types = text/plain;application/zip +activity_version = 1 +license = GPLv2+ diff --git a/examples/JS_examples/New_Style_Toolbars/activity/read-etexts.svg b/examples/JS_examples/New_Style_Toolbars/activity/read-etexts.svg new file mode 100644 index 0000000..5682ec8 --- /dev/null +++ b/examples/JS_examples/New_Style_Toolbars/activity/read-etexts.svg @@ -0,0 +1,71 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#000000"> + <!ENTITY fill_color "#FFFFFF"> +]><svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="48px" + height="48px" + id="svg116" + sodipodi:version="0.32" + inkscape:version="0.46+devel" + sodipodi:docname="New document 4" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs118"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="48 : 24 : 1" + inkscape:persp3d-origin="24 : 16 : 1" + id="perspective124" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="7" + inkscape:cx="24" + inkscape:cy="24" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:grid-bbox="true" + inkscape:document-units="px" + inkscape:window-width="1024" + inkscape:window-height="698" + inkscape:window-x="0" + inkscape:window-y="25" /> + <metadata + id="metadata121"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:label="Layer 1" + inkscape:groupmode="layer"> + <rect + style="fill:&fill_color;;stroke:&stroke_color;;stroke-opacity:1" + id="rect904" + width="36.142857" + height="32.142857" + x="4.1428571" + y="7.1428571" /> + </g> +</svg> diff --git a/examples/JS_examples/New_Style_Toolbars/mybutton.py b/examples/JS_examples/New_Style_Toolbars/mybutton.py new file mode 100644 index 0000000..38a7acb --- /dev/null +++ b/examples/JS_examples/New_Style_Toolbars/mybutton.py @@ -0,0 +1,52 @@ +# mybutton.py A version of ActivityToolbarButton that hides the "Keep" +# button. + +# Copyright (C) 2010 James D. Simmons +# +# 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 US +import gtk +import gconf + +from sugar.graphics.toolbarbox import ToolbarButton +from sugar.activity.widgets import ActivityToolbar +from sugar.graphics.xocolor import XoColor +from sugar.graphics.icon import Icon +from sugar.bundle.activitybundle import ActivityBundle + +def _create_activity_icon(metadata): + if metadata.get('icon-color', ''): + color = XoColor(metadata['icon-color']) + else: + client = gconf.client_get_default() + color = XoColor(client.get_string('/desktop/sugar/user/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 MyActivityToolbarButton(ToolbarButton): + + def __init__(self, activity, **kwargs): + toolbar = ActivityToolbar(activity, orientation_left=True) + toolbar.stop.hide() + toolbar.keep.hide() + + ToolbarButton.__init__(self, page=toolbar, **kwargs) + + icon = _create_activity_icon(activity.metadata) + self.set_icon_widget(icon) + icon.show() diff --git a/examples/JS_examples/New_Style_Toolbars/po/ReadEtextsIV.pot b/examples/JS_examples/New_Style_Toolbars/po/ReadEtextsIV.pot new file mode 100644 index 0000000..7d32f32 --- /dev/null +++ b/examples/JS_examples/New_Style_Toolbars/po/ReadEtextsIV.pot @@ -0,0 +1,61 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-03-05 10:39-0600\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: activity/activity.info:2 +msgid "ReadEtexts IV" +msgstr "" + +#: ReadEtextsActivity4.py:182 +msgid "Edit" +msgstr "" + +#: ReadEtextsActivity4.py:186 +msgid "Read" +msgstr "" + +#: ReadEtextsActivity4.py:194 +msgid "View" +msgstr "" + +#: ReadEtextsActivity4.py:240 toolbar.py:36 +msgid "Back" +msgstr "" + +#: ReadEtextsActivity4.py:247 toolbar.py:42 +msgid "Forward" +msgstr "" + +#: ReadEtextsActivity4.py:705 +msgid "Failure" +msgstr "" + +#: ReadEtextsActivity4.py:705 +msgid "Error getting document from tube" +msgstr "" + +#: toolbar.py:117 +msgid "Zoom out" +msgstr "" + +#: toolbar.py:122 +msgid "Zoom in" +msgstr "" + +#: toolbar.py:132 +msgid "Fullscreen" +msgstr "" diff --git a/examples/JS_examples/New_Style_Toolbars/setup.py b/examples/JS_examples/New_Style_Toolbars/setup.py new file mode 100755 index 0000000..ebc201d --- /dev/null +++ b/examples/JS_examples/New_Style_Toolbars/setup.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +# setup.py + +# Copyright (C) 2006, Red Hat, Inc. +# +# 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 + +from sugar.activity import bundlebuilder + +bundlebuilder.start() diff --git a/examples/JS_examples/New_Style_Toolbars/toolbar.py b/examples/JS_examples/New_Style_Toolbars/toolbar.py new file mode 100644 index 0000000..564602c --- /dev/null +++ b/examples/JS_examples/New_Style_Toolbars/toolbar.py @@ -0,0 +1,138 @@ +# toolbar.,py The toolbars used by ReadEtextsActivity. +# +# Copyright (C) 2010, James Simmons. +# Adapted from code Copyright (C) Red Hat Inc. +# +# 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 + +from gettext import gettext as _ +import re + +import pango +import gobject +import gtk + +from sugar.graphics.toolbutton import ToolButton + +class ReadToolbar(gtk.Toolbar): + __gtype_name__ = 'ReadToolbar' + + def __init__(self): + gtk.Toolbar.__init__(self) + + self.back = ToolButton('go-previous') + self.back.set_tooltip(_('Back')) + self.back.props.sensitive = False + self.insert(self.back, -1) + self.back.show() + + self.forward = ToolButton('go-next') + self.forward.set_tooltip(_('Forward')) + self.forward.props.sensitive = False + self.insert(self.forward, -1) + self.forward.show() + + num_page_item = gtk.ToolItem() + + self.num_page_entry = gtk.Entry() + self.num_page_entry.set_text('0') + self.num_page_entry.set_alignment(1) + self.num_page_entry.connect('insert-text', + self.num_page_entry_insert_text_cb) + + self.num_page_entry.set_width_chars(4) + + num_page_item.add(self.num_page_entry) + self.num_page_entry.show() + + self.insert(num_page_item, -1) + num_page_item.show() + + total_page_item = gtk.ToolItem() + + self.total_page_label = gtk.Label() + + label_attributes = pango.AttrList() + label_attributes.insert(pango.AttrSize(14000, 0, -1)) + label_attributes.insert(pango.AttrForeground(65535, 65535, 65535, 0, -1)) + self.total_page_label.set_attributes(label_attributes) + + self.total_page_label.set_text(' / 0') + total_page_item.add(self.total_page_label) + self.total_page_label.show() + + self.insert(total_page_item, -1) + total_page_item.show() + + def num_page_entry_insert_text_cb(self, entry, text, length, position): + if not re.match('[0-9]', text): + entry.emit_stop_by_name('insert-text') + return True + return False + + def update_nav_buttons(self): + current_page = self.current_page + self.back.props.sensitive = current_page > 0 + self.forward.props.sensitive = \ + current_page < self.total_pages - 1 + + self.num_page_entry.props.text = str(current_page + 1) + self.total_page_label.props.label = \ + ' / ' + str(self.total_pages) + + def set_total_pages(self, pages): + self.total_pages = pages + + def set_current_page(self, page): + self.current_page = page + self.update_nav_buttons() + +class ViewToolbar(gtk.Toolbar): + __gtype_name__ = 'ViewToolbar' + + __gsignals__ = { + 'needs-update-size': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])), + 'go-fullscreen': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])) + } + + def __init__(self): + gtk.Toolbar.__init__(self) + self.zoom_out = ToolButton('zoom-out') + self.zoom_out.set_tooltip(_('Zoom out')) + self.insert(self.zoom_out, -1) + self.zoom_out.show() + + self.zoom_in = ToolButton('zoom-in') + self.zoom_in.set_tooltip(_('Zoom in')) + self.insert(self.zoom_in, -1) + self.zoom_in.show() + + spacer = gtk.SeparatorToolItem() + spacer.props.draw = False + self.insert(spacer, -1) + spacer.show() + + self.fullscreen = ToolButton('view-fullscreen') + self.fullscreen.set_tooltip(_('Fullscreen')) + self.fullscreen.connect('clicked', self.fullscreen_cb) + self.insert(self.fullscreen, -1) + self.fullscreen.show() + + def fullscreen_cb(self, button): + self.emit('go-fullscreen') diff --git a/examples/SugarCommander.activity b/examples/SugarCommander.activity deleted file mode 160000 -Subproject 8bcdf7300169ce849221ffcd7b92791a5cd8e34 diff --git a/examples/SugarCommander.activity/.gitignore b/examples/SugarCommander.activity/.gitignore new file mode 100644 index 0000000..68d0d43 --- /dev/null +++ b/examples/SugarCommander.activity/.gitignore @@ -0,0 +1,6 @@ +.eric4project/ +.ropeproject/ +*.e4p +*.pyc +dist/ + diff --git a/examples/SugarCommander.activity/MANIFEST b/examples/SugarCommander.activity/MANIFEST new file mode 100644 index 0000000..6c3c796 --- /dev/null +++ b/examples/SugarCommander.activity/MANIFEST @@ -0,0 +1,5 @@ +sugarcommander.py +setup.py +activity/activity.info +activity/scommander.svg +po/SugarCommander.pot diff --git a/examples/SugarCommander.activity/activity/activity.info b/examples/SugarCommander.activity/activity/activity.info new file mode 100644 index 0000000..815e9c8 --- /dev/null +++ b/examples/SugarCommander.activity/activity/activity.info @@ -0,0 +1,8 @@ +[Activity] +name = Sugar Commander +service_name = org.laptop.sugar.SugarCommander +icon = scommander +exec = sugar-activity sugarcommander.SugarCommander +show_launcher = yes +activity_version = 3 +license = GPLv2+ diff --git a/examples/SugarCommander.activity/activity/scommander.svg b/examples/SugarCommander.activity/activity/scommander.svg new file mode 100644 index 0000000..67fc3b8 --- /dev/null +++ b/examples/SugarCommander.activity/activity/scommander.svg @@ -0,0 +1,88 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#000000"> + <!ENTITY fill_color "#ffffff"> +]> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + enable-background="new 0 0 55 55" + height="55px" + id="Layer_1" + version="1.1" + viewBox="0 0 55 55" + width="55px" + x="0px" + xml:space="preserve" + y="0px" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="scommander.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata + id="metadata2457"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs2455"><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 27.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="55 : 27.5 : 1" + inkscape:persp3d-origin="27.5 : 18.333333 : 1" + id="perspective2459" /></defs><sodipodi:namedview + inkscape:window-height="816" + inkscape:window-width="1152" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="&fill_color;" + id="base" + showgrid="false" + inkscape:zoom="11.313708" + inkscape:cx="27.5" + inkscape:cy="27.5" + inkscape:window-x="-4" + inkscape:window-y="-4" + inkscape:current-layer="Layer_1" /><g + display="block" + id="activity-journal" + transform="translate(0.1767767,-0.7071068)" + style="display:block"> + <path + d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 L 12.077,49 L 12.077,6 L 41.535,6 C 43.685,6 45.867,8.154 45.867,10.33 L 45.866,44.669 L 45.866,44.669 z" + id="path2444" + style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" /> + + <line + x1="21.341" + x2="21.341" + y1="6.1209998" + y2="48.881001" + id="line2446" + style="fill:none;stroke:&stroke_color;;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" /> + <path + d="M 7.384,14.464 C 7.384,14.464 9.468,15.159 11.554,15.159 C 13.64,15.159 15.727,14.464 15.727,14.464" + id="path2448" + style="fill:none;stroke:&stroke_color;;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" /> + <path + d="M 7.384,28.021 C 7.384,28.021 9.296,28.716 11.729,28.716 C 14.162,28.716 15.728,28.021 15.728,28.021" + id="path2450" + style="fill:none;stroke:&stroke_color;;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" /> + <path + d="M 7.384,41.232 C 7.384,41.232 9.12,41.927 11.902,41.927 C 14.683,41.927 15.727,41.232 15.727,41.232" + id="path2452" + style="fill:none;stroke:&stroke_color;;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" /> +</g><path + style="fill:none;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 26.958446,28.660272 C 31.819805,23.091806 31.819805,23.091806 31.819805,23.091806 C 36.504388,28.129942 36.504388,28.129942 36.504388,28.129942" + id="path2469" /><path + style="fill:none;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 26.870058,32.726136 C 31.643028,26.715729 31.643028,26.715729 31.643028,26.715729 C 35.974058,32.195806 35.974058,32.195806 35.974058,32.195806" + id="path2471" /></svg> diff --git a/examples/SugarCommander.activity/po/SugarCommander.pot b/examples/SugarCommander.activity/po/SugarCommander.pot new file mode 100644 index 0000000..844c8f9 --- /dev/null +++ b/examples/SugarCommander.activity/po/SugarCommander.pot @@ -0,0 +1,66 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-03-24 18:52-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: activity/activity.info:2 +msgid "Sugar Commander" +msgstr "" + +#: sugarcommander.py:58 sugarcommander.py:102 +msgid "Title" +msgstr "" + +#: sugarcommander.py:62 +msgid "MIME" +msgstr "" + +#: sugarcommander.py:74 +msgid "Journal" +msgstr "" + +#: sugarcommander.py:86 +msgid "Save" +msgstr "" + +#: sugarcommander.py:92 +msgid "Delete" +msgstr "" + +#: sugarcommander.py:113 +msgid "Description" +msgstr "" + +#: sugarcommander.py:126 +msgid "Tags" +msgstr "" + +#: sugarcommander.py:160 +msgid "Copy File To The Journal" +msgstr "" + +#: sugarcommander.py:164 +msgid "Files" +msgstr "" + +#: sugarcommander.py:345 +msgid "Success" +msgstr "" + +#: sugarcommander.py:345 +#, python-format +msgid "%s added to Journal." +msgstr "" diff --git a/examples/SugarCommander.activity/setup.py b/examples/SugarCommander.activity/setup.py new file mode 100755 index 0000000..d3ab3a3 --- /dev/null +++ b/examples/SugarCommander.activity/setup.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +# Copyright (C) 2006, Red Hat, Inc.
+# +# 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
+ +from sugar.activity import bundlebuilder + +bundlebuilder.start() diff --git a/examples/SugarCommander.activity/sugarcommander.py b/examples/SugarCommander.activity/sugarcommander.py new file mode 100755 index 0000000..28453d0 --- /dev/null +++ b/examples/SugarCommander.activity/sugarcommander.py @@ -0,0 +1,536 @@ +# SugarCommander.py + +# Copyright (C) 2010 James D. Simmons +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +import logging +import os +import gtk +import pango +import zipfile +from sugar import mime +from sugar.activity import activity +from sugar.datastore import datastore +from sugar.graphics.alert import NotifyAlert +from sugar.graphics import style +from gettext import gettext as _ +import gobject +import dbus + +COLUMN_TITLE = 0 +COLUMN_MIME = 1 +COLUMN_JOBJECT = 2 + +DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore' +DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore' +DS_DBUS_PATH = '/org/laptop/sugar/DataStore' + +_logger = logging.getLogger('sugar-commander') + +class SugarCommander(activity.Activity): + def __init__(self, handle, create_jobject=True): + "The entry point to the Activity" + activity.Activity.__init__(self, handle, False) + self.selected_journal_entry = None + self.selected_path = None + + canvas = gtk.Notebook() + canvas.props.show_border = True + canvas.props.show_tabs = True + canvas.show() + + self.ls_journal = gtk.ListStore(gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_PYOBJECT) + tv_journal = gtk.TreeView(self.ls_journal) + tv_journal.set_rules_hint(True) + tv_journal.set_search_column(COLUMN_TITLE) + self.selection_journal = tv_journal.get_selection() + self.selection_journal.set_mode(gtk.SELECTION_BROWSE) + self.selection_journal.connect("changed", self.selection_journal_cb) + renderer = gtk.CellRendererText() + renderer.set_property('wrap-mode', gtk.WRAP_WORD) + renderer.set_property('wrap-width', 500) + renderer.set_property('width', 500) + self.col_journal = gtk.TreeViewColumn(_('Title'), renderer, + text=COLUMN_TITLE) + self.col_journal.set_sort_column_id(COLUMN_TITLE) + tv_journal.append_column(self.col_journal) + + self.col_mime = gtk.TreeViewColumn(_('MIME'), renderer, + text=COLUMN_MIME) + self.col_mime.set_sort_column_id(COLUMN_MIME) + tv_journal.append_column(self.col_mime) + + self.list_scroller_journal = gtk.ScrolledWindow( + hadjustment=None, vadjustment=None) + self.list_scroller_journal.set_policy( + gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.list_scroller_journal.add(tv_journal) + + label_attributes = pango.AttrList() + label_attributes.insert(pango.AttrSize(14000, 0, -1)) + label_attributes.insert(pango.AttrForeground(65535, 65535, 65535, 0, -1)) + + tab1_label = gtk.Label(_("Journal")) + tab1_label.set_attributes(label_attributes) + tab1_label.show() + tv_journal.show() + self.list_scroller_journal.show() + + column_table = gtk.Table(rows=1, columns=2, homogeneous = False) + + image_table = gtk.Table(rows=2, columns=2, homogeneous=False) + self.image = gtk.Image() + image_table.attach(self.image, 0, 2, 0, 1, xoptions=gtk.FILL|gtk.SHRINK, + yoptions=gtk.FILL|gtk.SHRINK, xpadding=10, ypadding=10) + + self.btn_save = gtk.Button(_("Save")) + self.btn_save.connect('button_press_event', + self.save_button_press_event_cb) + image_table.attach(self.btn_save, 0, 1, 1, 2, xoptions=gtk.SHRINK, + yoptions=gtk.SHRINK, xpadding=10, ypadding=10) + self.btn_save.props.sensitive = False + self.btn_save.show() + + self.btn_delete = gtk.Button(_("Delete")) + self.btn_delete.connect('button_press_event', + self.delete_button_press_event_cb) + image_table.attach(self.btn_delete, 1, 2, 1, 2, xoptions=gtk.SHRINK, + yoptions=gtk.SHRINK, xpadding=10, ypadding=10) + self.btn_delete.props.sensitive = False + self.btn_delete.show() + + column_table.attach(image_table, 0, 1, 0, 1, + xoptions=gtk.FILL|gtk.SHRINK, + yoptions=gtk.SHRINK, xpadding=10, ypadding=10) + + entry_table = gtk.Table(rows=3, columns=2, + homogeneous=False) + + title_label = gtk.Label(_("Title")) + entry_table.attach(title_label, 0, 1, 0, 1, + xoptions=gtk.SHRINK, + yoptions=gtk.SHRINK, + xpadding=10, ypadding=10) + title_label.show() + + self.title_entry = gtk.Entry(max=0) + entry_table.attach(self.title_entry, 1, 2, 0, 1, + xoptions=gtk.FILL|gtk.SHRINK, + yoptions=gtk.SHRINK, xpadding=10, ypadding=10) + self.title_entry.connect('key_press_event', + self.key_press_event_cb) + self.title_entry.show() + + description_label = gtk.Label(_("Description")) + entry_table.attach(description_label, 0, 1, 1, 2, + xoptions=gtk.SHRINK, + yoptions=gtk.SHRINK, + xpadding=10, ypadding=10) + description_label.show() + + self.description_textview = gtk.TextView() + self.description_textview.set_wrap_mode(gtk.WRAP_WORD) + entry_table.attach(self.description_textview, 1, 2, 1, 2, + xoptions=gtk.EXPAND|gtk.FILL|gtk.SHRINK, + yoptions=gtk.EXPAND|gtk.FILL|gtk.SHRINK, + xpadding=10, ypadding=10) + self.description_textview.props.accepts_tab = False + self.description_textview.connect('key_press_event', + self.key_press_event_cb) + self.description_textview.show() + + tags_label = gtk.Label(_("Tags")) + entry_table.attach(tags_label, 0, 1, 2, 3, + xoptions=gtk.SHRINK, + yoptions=gtk.SHRINK, + xpadding=10, ypadding=10) + tags_label.show() + + self.tags_textview = gtk.TextView() + self.tags_textview.set_wrap_mode(gtk.WRAP_WORD) + entry_table.attach(self.tags_textview, 1, 2, 2, 3, + xoptions=gtk.FILL, + yoptions=gtk.EXPAND|gtk.FILL, + xpadding=10, ypadding=10) + self.tags_textview.props.accepts_tab = False + self.tags_textview.connect('key_press_event', + self.key_press_event_cb) + self.tags_textview.show() + + entry_table.show() + + self.scroller_entry = gtk.ScrolledWindow( + hadjustment=None, vadjustment=None) + self.scroller_entry.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self.scroller_entry.add_with_viewport(entry_table) + self.scroller_entry.show() + + column_table.attach(self.scroller_entry, 1, 2, 0, 1, + xoptions=gtk.FILL|gtk.EXPAND|gtk.SHRINK, + yoptions=gtk.FILL|gtk.EXPAND|gtk.SHRINK, + xpadding=10, ypadding=10) + image_table.show() + column_table.show() + + vbox = gtk.VBox(homogeneous=True, spacing=5) + vbox.pack_start(column_table) + vbox.pack_end(self.list_scroller_journal) + + canvas.append_page(vbox, tab1_label) + + self._filechooser = gtk.FileChooserWidget( + action=gtk.FILE_CHOOSER_ACTION_OPEN, backend=None) + self._filechooser.set_current_folder("/media") + self.copy_button = gtk.Button(_("Copy File To The Journal")) + self.copy_button.connect('clicked', self.create_journal_entry) + self.copy_button.show() + self._filechooser.set_extra_widget(self.copy_button) + preview = gtk.Image() + self._filechooser.set_preview_widget(preview) + self._filechooser.connect("update-preview", + self.update_preview_cb, preview) + tab2_label = gtk.Label(_("Files")) + tab2_label.set_attributes(label_attributes) + tab2_label.show() + canvas.append_page(self._filechooser, tab2_label) + + self.set_canvas(canvas) + self.show_all() + + toolbox = activity.ActivityToolbox(self) + activity_toolbar = toolbox.get_activity_toolbar() + activity_toolbar.keep.props.visible = False + activity_toolbar.share.props.visible = False + self.set_toolbox(toolbox) + toolbox.show() + + self.load_journal_table() + + bus = dbus.SessionBus() + remote_object = bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH) + _datastore = dbus.Interface(remote_object, DS_DBUS_INTERFACE) + _datastore.connect_to_signal('Created', self.datastore_created_cb) + _datastore.connect_to_signal('Updated', self.datastore_updated_cb) + _datastore.connect_to_signal('Deleted', self.datastore_deleted_cb) + + self.selected_journal_entry = None + + def update_preview_cb(self, file_chooser, preview): + filename = file_chooser.get_preview_filename() + file_mimetype = mime.get_for_file(filename) + if file_mimetype.startswith('image/'): + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, + style.zoom(320), style.zoom(240)) + preview.set_from_pixbuf(pixbuf) + have_preview = True + elif file_mimetype == 'application/x-cbz': + fname = self.extract_image(filename) + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(fname, + style.zoom(320), style.zoom(240)) + preview.set_from_pixbuf(pixbuf) + have_preview = True + os.remove(fname) + else: + have_preview = False + file_chooser.set_preview_widget_active(have_preview) + return + + def key_press_event_cb(self, entry, event): + self.btn_save.props.sensitive = True + + def save_button_press_event_cb(self, entry, event): + self.update_entry() + + def delete_button_press_event_cb(self, entry, event): + datastore.delete(self.selected_journal_entry.object_id) + + def datastore_created_cb(self, uid): + self.load_journal_table() + + def datastore_updated_cb(self, uid): + self.load_journal_table() + object_id = self.selected_journal_entry.object_id + jobject = datastore.get(object_id) + self.set_form_fields(jobject) + + def datastore_deleted_cb(self, uid): + self.load_journal_table() + object_id = self.selected_journal_entry.object_id + try: + jobject = datastore.get(object_id) + except: + if not self.selected_path is None: + self.selection_journal.select_path(self.selected_path) + else: + self.title_entry.set_text('') + description_textbuffer = self.description_textview.get_buffer() + description_textbuffer.set_text('') + tags_textbuffer = self.tags_textview.get_buffer() + tags_textbuffer.set_text('') + self.btn_save.props.sensitive = False + self.btn_delete.props.sensitive = False + self.image.clear() + self.image.show() + + def update_entry(self): + needs_update = False + needs_reload = False + + if self.selected_journal_entry is None: + return + + object_id = self.selected_journal_entry.object_id + jobject = datastore.get(object_id) + + old_title = jobject.metadata.get('title', None) + if old_title != self.title_entry.props.text: + jobject.metadata['title'] = self.title_entry.props.text + jobject.metadata['title_set_by_user'] = '1' + needs_update = True + needs_reload = True + + old_tags = jobject.metadata.get('tags', None) + new_tags = self.tags_textview.props.buffer.props.text + if old_tags != new_tags: + jobject.metadata['tags'] = new_tags + needs_update = True + + old_description = jobject.metadata.get('description', None) + new_description = self.description_textview.props.buffer.props.text + if old_description != new_description: + jobject.metadata['description'] = new_description + needs_update = True + + if needs_update: + datastore.write(jobject, update_mtime=False, + reply_handler=self.datastore_write_cb, + error_handler=self.datastore_write_error_cb) + if needs_reload: + self.load_journal_table() + + self.btn_save.props.sensitive = False + + def datastore_write_cb(self): + pass + + def datastore_write_error_cb(self, error): + logging.error('sugarcommander.datastore_write_error_cb: %r' % error) + + def close(self, skip_save=False): + "Override the close method so we don't try to create a Journal entry." + activity.Activity.close(self, True) + + def selection_journal_cb(self, selection): + self.btn_delete.props.sensitive = True + tv = selection.get_tree_view() + model = tv.get_model() + sel = selection.get_selected() + if sel: + model, iter = sel + jobject = model.get_value(iter,COLUMN_JOBJECT) + jobject = datastore.get(jobject.object_id) + self.selected_journal_entry = jobject + self.set_form_fields(jobject) + self.selected_path = model.get_path(iter) + + def set_form_fields(self, jobject): + self.title_entry.set_text(jobject.metadata['title']) + description_textbuffer = self.description_textview.get_buffer() + if jobject.metadata.has_key('description'): + description_textbuffer.set_text(jobject.metadata['description']) + else: + description_textbuffer.set_text('') + tags_textbuffer = self.tags_textview.get_buffer() + if jobject.metadata.has_key('tags'): + tags_textbuffer.set_text(jobject.metadata['tags']) + else: + tags_textbuffer.set_text('') + self.create_preview(jobject.object_id) + + def create_preview(self, object_id): + jobject = datastore.get(object_id) + + if jobject.metadata.has_key('preview'): + preview = jobject.metadata['preview'] + if preview is None or preview == '' or preview == 'None': + if jobject.metadata['mime_type'] .startswith('image/'): + filename = jobject.get_file_path() + self.show_image(filename) + return + if jobject.metadata['mime_type'] == 'application/x-cbz': + filename = jobject.get_file_path() + fname = self.extract_image(filename) + self.show_image(fname) + os.remove(fname) + return + + if jobject.metadata.has_key('preview') and \ + len(jobject.metadata['preview']) > 4: + + if jobject.metadata['preview'][1:4] == 'PNG': + preview_data = jobject.metadata['preview'] + else: + import base64 + preview_data = base64.b64decode(jobject.metadata['preview']) + + loader = gtk.gdk.PixbufLoader() + loader.write(preview_data) + scaled_buf = loader.get_pixbuf() + loader.close() + self.image.set_from_pixbuf(scaled_buf) + self.image.show() + else: + self.image.clear() + self.image.show() + + def load_journal_table(self): + self.btn_save.props.sensitive = False + self.btn_delete.props.sensitive = False + ds_mounts = datastore.mounts() + mountpoint_id = None + if len(ds_mounts) == 1 and ds_mounts[0]['id'] == 1: + pass + else: + for mountpoint in ds_mounts: + id = mountpoint['id'] + uri = mountpoint['uri'] + if uri.startswith('/home'): + mountpoint_id = id + + query = {} + if mountpoint_id is not None: + query['mountpoints'] = [ mountpoint_id ] + ds_objects, num_objects = datastore.find(query, properties=['uid', + 'title', 'mime_type']) + + self.ls_journal.clear() + for i in xrange (0, num_objects, 1): + iter = self.ls_journal.append() + title = ds_objects[i].metadata['title'] + self.ls_journal.set(iter, COLUMN_TITLE, title) + mime = ds_objects[i].metadata['mime_type'] + self.ls_journal.set(iter, COLUMN_MIME, mime) + self.ls_journal.set(iter, COLUMN_JOBJECT, ds_objects[i]) + if not self.selected_journal_entry is None and \ + self.selected_journal_entry.object_id == ds_objects[i].object_id: + self.selection_journal.select_iter(iter) + + self.ls_journal.set_sort_column_id(COLUMN_TITLE, gtk.SORT_ASCENDING) + v_adjustment = self.list_scroller_journal.get_vadjustment() + v_adjustment.value = 0 + return ds_objects[0] + + def create_journal_entry(self, widget, data=None): + filename = self._filechooser.get_filename() + journal_entry = datastore.create() + journal_entry.metadata['title'] = self.make_new_filename(filename) + journal_entry.metadata['title_set_by_user'] = '1' + journal_entry.metadata['keep'] = '0' + file_mimetype = mime.get_for_file(filename) + if not file_mimetype is None: + journal_entry.metadata['mime_type'] = file_mimetype + journal_entry.metadata['buddies'] = '' + if file_mimetype.startswith('image/'): + preview = self.create_preview_metadata(filename) + elif file_mimetype == 'application/x-cbz': + fname = self.extract_image(filename) + preview = self.create_preview_metadata(fname) + os.remove(fname) + else: + preview = '' + if not preview == '': + journal_entry.metadata['preview'] = dbus.ByteArray(preview) + else: + journal_entry.metadata['preview'] = '' + + journal_entry.file_path = filename + datastore.write(journal_entry) + self.alert(_('Success'), _('%s added to Journal.') + % self.make_new_filename(filename)) + + def alert(self, title, text=None): + alert = NotifyAlert(timeout=20) + alert.props.title = title + alert.props.msg = text + self.add_alert(alert) + alert.connect('response', self.alert_cancel_cb) + alert.show() + + def alert_cancel_cb(self, alert, response_id): + self.remove_alert(alert) + + def show_image(self, filename): + "display a resized image in a preview" + scaled_buf = gtk.gdk.pixbuf_new_from_file_at_size(filename, + style.zoom(320), style.zoom(240)) + self.image.set_from_pixbuf(scaled_buf) + self.image.show() + + def extract_image(self, filename): + zf = zipfile.ZipFile(filename, 'r') + image_files = zf.namelist() + image_files.sort() + if len(image_files) > 0: + if self.save_extracted_file(zf, image_files[0]): + fname = os.path.join(self.get_activity_root(), 'instance', + self.make_new_filename(image_files[0])) + return fname + + def save_extracted_file(self, zipfile, filename): + "Extract the file to a temp directory for viewing" + try: + filebytes = zipfile.read(filename) + except zipfile.BadZipfile, err: + print 'Error opening the zip file: %s' % (err) + return False + except KeyError, err: + self.alert('Key Error', 'Zipfile key not found: ' + + str(filename)) + return + outfn = self.make_new_filename(filename) + if (outfn == ''): + return False + fname = os.path.join(self.get_activity_root(), 'instance', outfn) + f = open(fname, 'w') + try: + f.write(filebytes) + finally: + f.close() + return True + + def make_new_filename(self, filename): + partition_tuple = filename.rpartition('/') + return partition_tuple[2] + + def create_preview_metadata(self, filename): + + file_mimetype = mime.get_for_file(filename) + if not file_mimetype.startswith('image/'): + return '' + + scaled_pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, + style.zoom(320), style.zoom(240)) + preview_data = [] + + def save_func(buf, data): + data.append(buf) + + scaled_pixbuf.save_to_callback(save_func, 'png', + user_data=preview_data) + preview_data = ''.join(preview_data) + + return preview_data |