Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorroot <root@ghunt-desktop.(none)>2010-12-20 16:54:46 (GMT)
committer root <root@ghunt-desktop.(none)>2010-12-20 16:54:46 (GMT)
commit750ced1ac69eef6c15c4d404d8b4c8c698663859 (patch)
treefe47a3fddf84742f8d7f6cfc435bb9c9517c64c3 /examples
parent16fc1eeb4fb1673d0e12338a5d726185bd72431a (diff)
add PyDebug.pot, do html cleanup
Diffstat (limited to 'examples')
-rw-r--r--examples/Making_Activities/.gitignore7
-rw-r--r--examples/Making_Activities/Add_Refinements/MANIFEST6
-rw-r--r--examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/MANIFEST6
-rw-r--r--examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/ReadEtextsActivity2.py328
-rw-r--r--examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/activity/activity.info9
-rw-r--r--examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/activity/activity.info~9
-rw-r--r--examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/activity/read-etexts.svg71
-rw-r--r--examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/po/ReadEtextsII.pot53
-rwxr-xr-xexamples/Making_Activities/Add_Refinements/ReadEtexts II.activity/setup.py23
-rw-r--r--examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/toolbar.py138
-rw-r--r--examples/Making_Activities/Add_Refinements/ReadEtextsActivity2.py328
-rw-r--r--examples/Making_Activities/Add_Refinements/activity/activity.info9
-rw-r--r--examples/Making_Activities/Add_Refinements/activity/activity.info~9
-rw-r--r--examples/Making_Activities/Add_Refinements/activity/read-etexts.svg71
-rw-r--r--examples/Making_Activities/Add_Refinements/po/ReadEtextsII.pot53
-rwxr-xr-xexamples/Making_Activities/Add_Refinements/setup.py23
-rw-r--r--examples/Making_Activities/Add_Refinements/toolbar.py138
-rwxr-xr-xexamples/Making_Activities/First_Standalone/ReadEtexts.py197
-rw-r--r--examples/Making_Activities/MiniChat/MANIFEST10
-rw-r--r--examples/Making_Activities/MiniChat/Mini Chat.activity/MANIFEST10
-rw-r--r--examples/Making_Activities/MiniChat/Mini Chat.activity/activity/activity.info9
-rw-r--r--examples/Making_Activities/MiniChat/Mini Chat.activity/activity/activity.info~9
-rw-r--r--examples/Making_Activities/MiniChat/Mini Chat.activity/activity/chat.svg6
-rw-r--r--examples/Making_Activities/MiniChat/Mini Chat.activity/minichat.py325
-rw-r--r--examples/Making_Activities/MiniChat/Mini Chat.activity/po/MiniChat.pot49
-rwxr-xr-xexamples/Making_Activities/MiniChat/Mini Chat.activity/setup.py23
-rw-r--r--examples/Making_Activities/MiniChat/Mini Chat.activity/textchannel.py137
-rw-r--r--examples/Making_Activities/MiniChat/activity/activity.info9
-rw-r--r--examples/Making_Activities/MiniChat/activity/activity.info~9
-rw-r--r--examples/Making_Activities/MiniChat/activity/chat.svg6
-rw-r--r--examples/Making_Activities/MiniChat/minichat.py325
-rw-r--r--examples/Making_Activities/MiniChat/po/MiniChat.pot49
-rwxr-xr-xexamples/Making_Activities/MiniChat/setup.py23
-rw-r--r--examples/Making_Activities/MiniChat/textchannel.py137
-rw-r--r--examples/Making_Activities/New_Toolbars/MANIFEST9
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/MANIFEST8
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/ReadEtextsActivity3.py640
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/activity/activity.info9
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/activity/activity.info~9
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/activity/read-etexts.svg71
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/po/ReadEtextsIII.pot61
-rwxr-xr-xexamples/Making_Activities/New_Toolbars/ReadEtexts III.activity/setup.py23
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/toolbar.py138
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/MANIFEST9
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/ReadEtextsActivity4.py805
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/activity/activity.info9
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/activity/activity.info~9
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/activity/read-etexts.svg71
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/mybutton.py52
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/po/ReadEtextsIV.pot61
-rwxr-xr-xexamples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/setup.py23
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/toolbar.py138
-rw-r--r--examples/Making_Activities/New_Toolbars/ReadEtextsActivity4.py805
-rw-r--r--examples/Making_Activities/New_Toolbars/activity/activity.info9
-rw-r--r--examples/Making_Activities/New_Toolbars/activity/activity.info~9
-rw-r--r--examples/Making_Activities/New_Toolbars/activity/read-etexts.svg71
-rw-r--r--examples/Making_Activities/New_Toolbars/mybutton.py52
-rw-r--r--examples/Making_Activities/New_Toolbars/po/ReadEtextsIV.pot61
-rwxr-xr-xexamples/Making_Activities/New_Toolbars/setup.py23
-rw-r--r--examples/Making_Activities/New_Toolbars/toolbar.py138
-rw-r--r--examples/Making_Activities/Shared_activities/MANIFEST8
-rw-r--r--examples/Making_Activities/Shared_activities/ReadEtextsActivity3.py640
-rw-r--r--examples/Making_Activities/Shared_activities/activity/activity.info9
-rw-r--r--examples/Making_Activities/Shared_activities/activity/activity.info~9
-rw-r--r--examples/Making_Activities/Shared_activities/activity/read-etexts.svg71
-rw-r--r--examples/Making_Activities/Shared_activities/po/ReadEtextsIII.pot61
-rwxr-xr-xexamples/Making_Activities/Shared_activities/setup.py23
-rw-r--r--examples/Making_Activities/Shared_activities/toolbar.py138
-rw-r--r--examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/MANIFEST7
-rw-r--r--examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/ReadEtextsActivity.py199
-rw-r--r--examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/activity/activity.info9
-rw-r--r--examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/activity/activity.info~9
-rw-r--r--examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/activity/read-etexts.svg71
-rw-r--r--examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/po/ReadEtextsI.pot21
-rwxr-xr-xexamples/Making_Activities/Subclass_Activity/ReadEtexts.activity/setup.py23
-rwxr-xr-xexamples/Making_Activities/Text_to_Speech/ReadEtextsTTS.py271
-rwxr-xr-xexamples/Making_Activities/Text_to_Speech/espeak.py63
-rwxr-xr-xexamples/Making_Activities/Text_to_Speech/gst_choir_example.py51
-rwxr-xr-xexamples/Making_Activities/Text_to_Speech/gst_simple_example.py36
-rwxr-xr-xexamples/Making_Activities/Text_to_Speech/gst_simple_tts.py93
-rwxr-xr-xexamples/Making_Activities/Text_to_Speech/gst_track_marks.py54
-rwxr-xr-xexamples/Making_Activities/Text_to_Speech/gst_track_words.py50
-rw-r--r--examples/Making_Activities/Text_to_Speech/speech.py136
-rw-r--r--examples/Making_Activities/Text_to_Speech/testtts.txt21
-rw-r--r--examples/Making_Activities/Using_PyGame/Demoiselle.activity/DemoiselleActivity.py103
-rw-r--r--examples/Making_Activities/Using_PyGame/Demoiselle.activity/MANIFEST17
-rw-r--r--examples/Making_Activities/Using_PyGame/Demoiselle.activity/activity/activity.info9
-rw-r--r--examples/Making_Activities/Using_PyGame/Demoiselle.activity/activity/activity.info~9
-rw-r--r--examples/Making_Activities/Using_PyGame/Demoiselle.activity/activity/read-etexts.svg71
-rw-r--r--examples/Making_Activities/Using_PyGame/Demoiselle.activity/demoiselle.pngbin0 -> 52729 bytes
-rwxr-xr-xexamples/Making_Activities/Using_PyGame/Demoiselle.activity/demoiselle.py166
-rwxr-xr-xexamples/Making_Activities/Using_PyGame/Demoiselle.activity/demoiselle2.py185
-rw-r--r--examples/Making_Activities/Using_PyGame/Demoiselle.activity/glider_hit.pngbin0 -> 8931 bytes
-rw-r--r--examples/Making_Activities/Using_PyGame/Demoiselle.activity/glider_normal.pngbin0 -> 8987 bytes
-rw-r--r--examples/Making_Activities/Using_PyGame/Demoiselle.activity/po/Demoiselle.pot3
-rwxr-xr-xexamples/Making_Activities/Using_PyGame/Demoiselle.activity/setup.py23
-rw-r--r--examples/Making_Activities/Using_PyGame/Demoiselle.activity/sky.jpgbin0 -> 813947 bytes
-rw-r--r--examples/Making_Activities/Using_PyGame/Demoiselle.activity/sugargame/__init__.py1
-rw-r--r--examples/Making_Activities/Using_PyGame/Demoiselle.activity/sugargame/canvas.py56
-rw-r--r--examples/Making_Activities/Using_PyGame/Demoiselle.activity/sugargame/event.py241
-rw-r--r--examples/Making_Activities/Using_PyGame/DemoiselleActivity.py103
-rw-r--r--examples/Making_Activities/Using_PyGame/MANIFEST17
-rw-r--r--examples/Making_Activities/Using_PyGame/activity/activity.info9
-rw-r--r--examples/Making_Activities/Using_PyGame/activity/activity.info~9
-rw-r--r--examples/Making_Activities/Using_PyGame/activity/read-etexts.svg71
-rw-r--r--examples/Making_Activities/Using_PyGame/demoiselle.pngbin0 -> 52729 bytes
-rwxr-xr-xexamples/Making_Activities/Using_PyGame/demoiselle.py166
-rwxr-xr-xexamples/Making_Activities/Using_PyGame/demoiselle2.py185
-rw-r--r--examples/Making_Activities/Using_PyGame/glider_hit.pngbin0 -> 8931 bytes
-rw-r--r--examples/Making_Activities/Using_PyGame/glider_normal.pngbin0 -> 8987 bytes
-rw-r--r--examples/Making_Activities/Using_PyGame/po/Demoiselle.pot3
-rwxr-xr-xexamples/Making_Activities/Using_PyGame/setup.py23
-rw-r--r--examples/Making_Activities/Using_PyGame/sky.jpgbin0 -> 813947 bytes
-rw-r--r--examples/Making_Activities/Using_PyGame/sugargame/__init__.py1
-rw-r--r--examples/Making_Activities/Using_PyGame/sugargame/canvas.py56
-rw-r--r--examples/Making_Activities/Using_PyGame/sugargame/event.py241
116 files changed, 9768 insertions, 0 deletions
diff --git a/examples/Making_Activities/.gitignore b/examples/Making_Activities/.gitignore
new file mode 100644
index 0000000..ca67c95
--- /dev/null
+++ b/examples/Making_Activities/.gitignore
@@ -0,0 +1,7 @@
+*.pyc
+*.e4p
+*.zip
+.eric4project/
+.ropeproject/
+dist
+
diff --git a/examples/Making_Activities/Add_Refinements/MANIFEST b/examples/Making_Activities/Add_Refinements/MANIFEST
new file mode 100644
index 0000000..e724c0c
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Add_Refinements/ReadEtexts II.activity/MANIFEST b/examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/MANIFEST
new file mode 100644
index 0000000..e724c0c
--- /dev/null
+++ b/examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/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/Making_Activities/Add_Refinements/ReadEtexts II.activity/ReadEtextsActivity2.py b/examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/ReadEtextsActivity2.py
new file mode 100644
index 0000000..d11be21
--- /dev/null
+++ b/examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/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/Making_Activities/Add_Refinements/ReadEtexts II.activity/activity/activity.info b/examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/activity/activity.info
new file mode 100644
index 0000000..738f496
--- /dev/null
+++ b/examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/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/Making_Activities/Add_Refinements/ReadEtexts II.activity/activity/activity.info~ b/examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/activity/activity.info~
new file mode 100644
index 0000000..137333f
--- /dev/null
+++ b/examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/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/Making_Activities/Add_Refinements/ReadEtexts II.activity/activity/read-etexts.svg b/examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/activity/read-etexts.svg
new file mode 100644
index 0000000..5682ec8
--- /dev/null
+++ b/examples/Making_Activities/Add_Refinements/ReadEtexts II.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/Making_Activities/Add_Refinements/ReadEtexts II.activity/po/ReadEtextsII.pot b/examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/po/ReadEtextsII.pot
new file mode 100644
index 0000000..0df8967
--- /dev/null
+++ b/examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/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/Making_Activities/Add_Refinements/ReadEtexts II.activity/setup.py b/examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/setup.py
new file mode 100755
index 0000000..ebc201d
--- /dev/null
+++ b/examples/Making_Activities/Add_Refinements/ReadEtexts II.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/Making_Activities/Add_Refinements/ReadEtexts II.activity/toolbar.py b/examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/toolbar.py
new file mode 100644
index 0000000..564602c
--- /dev/null
+++ b/examples/Making_Activities/Add_Refinements/ReadEtexts II.activity/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/Making_Activities/Add_Refinements/ReadEtextsActivity2.py b/examples/Making_Activities/Add_Refinements/ReadEtextsActivity2.py
new file mode 100644
index 0000000..d11be21
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Add_Refinements/activity/activity.info b/examples/Making_Activities/Add_Refinements/activity/activity.info
new file mode 100644
index 0000000..738f496
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Add_Refinements/activity/activity.info~ b/examples/Making_Activities/Add_Refinements/activity/activity.info~
new file mode 100644
index 0000000..137333f
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Add_Refinements/activity/read-etexts.svg b/examples/Making_Activities/Add_Refinements/activity/read-etexts.svg
new file mode 100644
index 0000000..5682ec8
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Add_Refinements/po/ReadEtextsII.pot b/examples/Making_Activities/Add_Refinements/po/ReadEtextsII.pot
new file mode 100644
index 0000000..0df8967
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Add_Refinements/setup.py b/examples/Making_Activities/Add_Refinements/setup.py
new file mode 100755
index 0000000..ebc201d
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Add_Refinements/toolbar.py b/examples/Making_Activities/Add_Refinements/toolbar.py
new file mode 100644
index 0000000..564602c
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/First_Standalone/ReadEtexts.py b/examples/Making_Activities/First_Standalone/ReadEtexts.py
new file mode 100755
index 0000000..9e3522f
--- /dev/null
+++ b/examples/Making_Activities/First_Standalone/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/Making_Activities/MiniChat/MANIFEST b/examples/Making_Activities/MiniChat/MANIFEST
new file mode 100644
index 0000000..51c6abf
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/MiniChat/Mini Chat.activity/MANIFEST b/examples/Making_Activities/MiniChat/Mini Chat.activity/MANIFEST
new file mode 100644
index 0000000..51c6abf
--- /dev/null
+++ b/examples/Making_Activities/MiniChat/Mini Chat.activity/MANIFEST
@@ -0,0 +1,10 @@
+
+setup.py
+
+
+
+activity/activity.info
+minichat.py
+textchannel.py
+activity/chat.svg
+po/MiniChat.pot
diff --git a/examples/Making_Activities/MiniChat/Mini Chat.activity/activity/activity.info b/examples/Making_Activities/MiniChat/Mini Chat.activity/activity/activity.info
new file mode 100644
index 0000000..689414c
--- /dev/null
+++ b/examples/Making_Activities/MiniChat/Mini Chat.activity/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/Making_Activities/MiniChat/Mini Chat.activity/activity/activity.info~ b/examples/Making_Activities/MiniChat/Mini Chat.activity/activity/activity.info~
new file mode 100644
index 0000000..8355763
--- /dev/null
+++ b/examples/Making_Activities/MiniChat/Mini Chat.activity/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/Making_Activities/MiniChat/Mini Chat.activity/activity/chat.svg b/examples/Making_Activities/MiniChat/Mini Chat.activity/activity/chat.svg
new file mode 100644
index 0000000..b0df51b
--- /dev/null
+++ b/examples/Making_Activities/MiniChat/Mini Chat.activity/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/Making_Activities/MiniChat/Mini Chat.activity/minichat.py b/examples/Making_Activities/MiniChat/Mini Chat.activity/minichat.py
new file mode 100644
index 0000000..ce48cea
--- /dev/null
+++ b/examples/Making_Activities/MiniChat/Mini Chat.activity/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/Making_Activities/MiniChat/Mini Chat.activity/po/MiniChat.pot b/examples/Making_Activities/MiniChat/Mini Chat.activity/po/MiniChat.pot
new file mode 100644
index 0000000..074f630
--- /dev/null
+++ b/examples/Making_Activities/MiniChat/Mini Chat.activity/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/Making_Activities/MiniChat/Mini Chat.activity/setup.py b/examples/Making_Activities/MiniChat/Mini Chat.activity/setup.py
new file mode 100755
index 0000000..ebc201d
--- /dev/null
+++ b/examples/Making_Activities/MiniChat/Mini Chat.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/Making_Activities/MiniChat/Mini Chat.activity/textchannel.py b/examples/Making_Activities/MiniChat/Mini Chat.activity/textchannel.py
new file mode 100644
index 0000000..1dfe1ac
--- /dev/null
+++ b/examples/Making_Activities/MiniChat/Mini Chat.activity/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/Making_Activities/MiniChat/activity/activity.info b/examples/Making_Activities/MiniChat/activity/activity.info
new file mode 100644
index 0000000..689414c
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/MiniChat/activity/activity.info~ b/examples/Making_Activities/MiniChat/activity/activity.info~
new file mode 100644
index 0000000..8355763
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/MiniChat/activity/chat.svg b/examples/Making_Activities/MiniChat/activity/chat.svg
new file mode 100644
index 0000000..b0df51b
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/MiniChat/minichat.py b/examples/Making_Activities/MiniChat/minichat.py
new file mode 100644
index 0000000..ce48cea
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/MiniChat/po/MiniChat.pot b/examples/Making_Activities/MiniChat/po/MiniChat.pot
new file mode 100644
index 0000000..074f630
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/MiniChat/setup.py b/examples/Making_Activities/MiniChat/setup.py
new file mode 100755
index 0000000..ebc201d
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/MiniChat/textchannel.py b/examples/Making_Activities/MiniChat/textchannel.py
new file mode 100644
index 0000000..1dfe1ac
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/New_Toolbars/MANIFEST b/examples/Making_Activities/New_Toolbars/MANIFEST
new file mode 100644
index 0000000..f45e662
--- /dev/null
+++ b/examples/Making_Activities/New_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/Making_Activities/New_Toolbars/ReadEtexts III.activity/MANIFEST b/examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/MANIFEST
new file mode 100644
index 0000000..a46aae0
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/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/Making_Activities/New_Toolbars/ReadEtexts III.activity/ReadEtextsActivity3.py b/examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/ReadEtextsActivity3.py
new file mode 100644
index 0000000..5513e2a
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/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/Making_Activities/New_Toolbars/ReadEtexts III.activity/activity/activity.info b/examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/activity/activity.info
new file mode 100644
index 0000000..0add223
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/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/Making_Activities/New_Toolbars/ReadEtexts III.activity/activity/activity.info~ b/examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/activity/activity.info~
new file mode 100644
index 0000000..137333f
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/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/Making_Activities/New_Toolbars/ReadEtexts III.activity/activity/read-etexts.svg b/examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/activity/read-etexts.svg
new file mode 100644
index 0000000..5682ec8
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts III.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/Making_Activities/New_Toolbars/ReadEtexts III.activity/po/ReadEtextsIII.pot b/examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/po/ReadEtextsIII.pot
new file mode 100644
index 0000000..fb2e8cb
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/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/Making_Activities/New_Toolbars/ReadEtexts III.activity/setup.py b/examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/setup.py
new file mode 100755
index 0000000..ebc201d
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts III.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/Making_Activities/New_Toolbars/ReadEtexts III.activity/toolbar.py b/examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/toolbar.py
new file mode 100644
index 0000000..564602c
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts III.activity/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/Making_Activities/New_Toolbars/ReadEtexts IV.activity/MANIFEST b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/MANIFEST
new file mode 100644
index 0000000..f45e662
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/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/Making_Activities/New_Toolbars/ReadEtexts IV.activity/ReadEtextsActivity4.py b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/ReadEtextsActivity4.py
new file mode 100644
index 0000000..ad5604a
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/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/Making_Activities/New_Toolbars/ReadEtexts IV.activity/activity/activity.info b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/activity/activity.info
new file mode 100644
index 0000000..7197bd8
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/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/Making_Activities/New_Toolbars/ReadEtexts IV.activity/activity/activity.info~ b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/activity/activity.info~
new file mode 100644
index 0000000..670a054
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/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/Making_Activities/New_Toolbars/ReadEtexts IV.activity/activity/read-etexts.svg b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/activity/read-etexts.svg
new file mode 100644
index 0000000..5682ec8
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.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/Making_Activities/New_Toolbars/ReadEtexts IV.activity/mybutton.py b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/mybutton.py
new file mode 100644
index 0000000..38a7acb
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/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/Making_Activities/New_Toolbars/ReadEtexts IV.activity/po/ReadEtextsIV.pot b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/po/ReadEtextsIV.pot
new file mode 100644
index 0000000..7d32f32
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/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/Making_Activities/New_Toolbars/ReadEtexts IV.activity/setup.py b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/setup.py
new file mode 100755
index 0000000..ebc201d
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.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/Making_Activities/New_Toolbars/ReadEtexts IV.activity/toolbar.py b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/toolbar.py
new file mode 100644
index 0000000..564602c
--- /dev/null
+++ b/examples/Making_Activities/New_Toolbars/ReadEtexts IV.activity/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/Making_Activities/New_Toolbars/ReadEtextsActivity4.py b/examples/Making_Activities/New_Toolbars/ReadEtextsActivity4.py
new file mode 100644
index 0000000..ad5604a
--- /dev/null
+++ b/examples/Making_Activities/New_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/Making_Activities/New_Toolbars/activity/activity.info b/examples/Making_Activities/New_Toolbars/activity/activity.info
new file mode 100644
index 0000000..7197bd8
--- /dev/null
+++ b/examples/Making_Activities/New_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/Making_Activities/New_Toolbars/activity/activity.info~ b/examples/Making_Activities/New_Toolbars/activity/activity.info~
new file mode 100644
index 0000000..670a054
--- /dev/null
+++ b/examples/Making_Activities/New_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/Making_Activities/New_Toolbars/activity/read-etexts.svg b/examples/Making_Activities/New_Toolbars/activity/read-etexts.svg
new file mode 100644
index 0000000..5682ec8
--- /dev/null
+++ b/examples/Making_Activities/New_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/Making_Activities/New_Toolbars/mybutton.py b/examples/Making_Activities/New_Toolbars/mybutton.py
new file mode 100644
index 0000000..38a7acb
--- /dev/null
+++ b/examples/Making_Activities/New_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/Making_Activities/New_Toolbars/po/ReadEtextsIV.pot b/examples/Making_Activities/New_Toolbars/po/ReadEtextsIV.pot
new file mode 100644
index 0000000..7d32f32
--- /dev/null
+++ b/examples/Making_Activities/New_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/Making_Activities/New_Toolbars/setup.py b/examples/Making_Activities/New_Toolbars/setup.py
new file mode 100755
index 0000000..ebc201d
--- /dev/null
+++ b/examples/Making_Activities/New_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/Making_Activities/New_Toolbars/toolbar.py b/examples/Making_Activities/New_Toolbars/toolbar.py
new file mode 100644
index 0000000..564602c
--- /dev/null
+++ b/examples/Making_Activities/New_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/Making_Activities/Shared_activities/MANIFEST b/examples/Making_Activities/Shared_activities/MANIFEST
new file mode 100644
index 0000000..a46aae0
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Shared_activities/ReadEtextsActivity3.py b/examples/Making_Activities/Shared_activities/ReadEtextsActivity3.py
new file mode 100644
index 0000000..5513e2a
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Shared_activities/activity/activity.info b/examples/Making_Activities/Shared_activities/activity/activity.info
new file mode 100644
index 0000000..0add223
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Shared_activities/activity/activity.info~ b/examples/Making_Activities/Shared_activities/activity/activity.info~
new file mode 100644
index 0000000..137333f
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Shared_activities/activity/read-etexts.svg b/examples/Making_Activities/Shared_activities/activity/read-etexts.svg
new file mode 100644
index 0000000..5682ec8
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Shared_activities/po/ReadEtextsIII.pot b/examples/Making_Activities/Shared_activities/po/ReadEtextsIII.pot
new file mode 100644
index 0000000..fb2e8cb
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Shared_activities/setup.py b/examples/Making_Activities/Shared_activities/setup.py
new file mode 100755
index 0000000..ebc201d
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Shared_activities/toolbar.py b/examples/Making_Activities/Shared_activities/toolbar.py
new file mode 100644
index 0000000..564602c
--- /dev/null
+++ b/examples/Making_Activities/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/Making_Activities/Subclass_Activity/ReadEtexts.activity/MANIFEST b/examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/MANIFEST
new file mode 100644
index 0000000..8cba1c5
--- /dev/null
+++ b/examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/MANIFEST
@@ -0,0 +1,7 @@
+
+setup.py
+ReadEtextsActivity.py
+
+activity/read-etexts.svg
+activity/activity.info
+po/ReadEtextsI.pot
diff --git a/examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/ReadEtextsActivity.py b/examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/ReadEtextsActivity.py
new file mode 100644
index 0000000..9a9372a
--- /dev/null
+++ b/examples/Making_Activities/Subclass_Activity/ReadEtexts.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/Making_Activities/Subclass_Activity/ReadEtexts.activity/activity/activity.info b/examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/activity/activity.info
new file mode 100644
index 0000000..e1cdf73
--- /dev/null
+++ b/examples/Making_Activities/Subclass_Activity/ReadEtexts.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/Making_Activities/Subclass_Activity/ReadEtexts.activity/activity/activity.info~ b/examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/activity/activity.info~
new file mode 100644
index 0000000..b696c1e
--- /dev/null
+++ b/examples/Making_Activities/Subclass_Activity/ReadEtexts.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/Making_Activities/Subclass_Activity/ReadEtexts.activity/activity/read-etexts.svg b/examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/activity/read-etexts.svg
new file mode 100644
index 0000000..5682ec8
--- /dev/null
+++ b/examples/Making_Activities/Subclass_Activity/ReadEtexts.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/Making_Activities/Subclass_Activity/ReadEtexts.activity/po/ReadEtextsI.pot b/examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/po/ReadEtextsI.pot
new file mode 100644
index 0000000..4ff74e6
--- /dev/null
+++ b/examples/Making_Activities/Subclass_Activity/ReadEtexts.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/Making_Activities/Subclass_Activity/ReadEtexts.activity/setup.py b/examples/Making_Activities/Subclass_Activity/ReadEtexts.activity/setup.py
new file mode 100755
index 0000000..ebc201d
--- /dev/null
+++ b/examples/Making_Activities/Subclass_Activity/ReadEtexts.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/Making_Activities/Text_to_Speech/ReadEtextsTTS.py b/examples/Making_Activities/Text_to_Speech/ReadEtextsTTS.py
new file mode 100755
index 0000000..28e3cfd
--- /dev/null
+++ b/examples/Making_Activities/Text_to_Speech/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/Making_Activities/Text_to_Speech/espeak.py b/examples/Making_Activities/Text_to_Speech/espeak.py
new file mode 100755
index 0000000..fbb1b22
--- /dev/null
+++ b/examples/Making_Activities/Text_to_Speech/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/Making_Activities/Text_to_Speech/gst_choir_example.py b/examples/Making_Activities/Text_to_Speech/gst_choir_example.py
new file mode 100755
index 0000000..9ea653f
--- /dev/null
+++ b/examples/Making_Activities/Text_to_Speech/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/Making_Activities/Text_to_Speech/gst_simple_example.py b/examples/Making_Activities/Text_to_Speech/gst_simple_example.py
new file mode 100755
index 0000000..6ae2707
--- /dev/null
+++ b/examples/Making_Activities/Text_to_Speech/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/Making_Activities/Text_to_Speech/gst_simple_tts.py b/examples/Making_Activities/Text_to_Speech/gst_simple_tts.py
new file mode 100755
index 0000000..34dcccf
--- /dev/null
+++ b/examples/Making_Activities/Text_to_Speech/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/Making_Activities/Text_to_Speech/gst_track_marks.py b/examples/Making_Activities/Text_to_Speech/gst_track_marks.py
new file mode 100755
index 0000000..5eecb78
--- /dev/null
+++ b/examples/Making_Activities/Text_to_Speech/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/Making_Activities/Text_to_Speech/gst_track_words.py b/examples/Making_Activities/Text_to_Speech/gst_track_words.py
new file mode 100755
index 0000000..fe61c8a
--- /dev/null
+++ b/examples/Making_Activities/Text_to_Speech/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/Making_Activities/Text_to_Speech/speech.py b/examples/Making_Activities/Text_to_Speech/speech.py
new file mode 100644
index 0000000..cdab7d2
--- /dev/null
+++ b/examples/Making_Activities/Text_to_Speech/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/Making_Activities/Text_to_Speech/testtts.txt b/examples/Making_Activities/Text_to_Speech/testtts.txt
new file mode 100644
index 0000000..4057792
--- /dev/null
+++ b/examples/Making_Activities/Text_to_Speech/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/Making_Activities/Using_PyGame/Demoiselle.activity/DemoiselleActivity.py b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/DemoiselleActivity.py
new file mode 100644
index 0000000..2c6c99f
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/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/Making_Activities/Using_PyGame/Demoiselle.activity/MANIFEST b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/MANIFEST
new file mode 100644
index 0000000..42b54df
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/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/Making_Activities/Using_PyGame/Demoiselle.activity/activity/activity.info b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/activity/activity.info
new file mode 100644
index 0000000..2767e12
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/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/Making_Activities/Using_PyGame/Demoiselle.activity/activity/activity.info~ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/activity/activity.info~
new file mode 100644
index 0000000..137333f
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/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/Making_Activities/Using_PyGame/Demoiselle.activity/activity/read-etexts.svg b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/activity/read-etexts.svg
new file mode 100644
index 0000000..5682ec8
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.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/Making_Activities/Using_PyGame/Demoiselle.activity/demoiselle.png b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/demoiselle.png
new file mode 100644
index 0000000..37db411
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/demoiselle.png
Binary files differ
diff --git a/examples/Making_Activities/Using_PyGame/Demoiselle.activity/demoiselle.py b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/demoiselle.py
new file mode 100755
index 0000000..93817e1
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/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/Making_Activities/Using_PyGame/Demoiselle.activity/demoiselle2.py b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/demoiselle2.py
new file mode 100755
index 0000000..46656ca
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/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/Making_Activities/Using_PyGame/Demoiselle.activity/glider_hit.png b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/glider_hit.png
new file mode 100644
index 0000000..49f0a8c
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/glider_hit.png
Binary files differ
diff --git a/examples/Making_Activities/Using_PyGame/Demoiselle.activity/glider_normal.png b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/glider_normal.png
new file mode 100644
index 0000000..8fa812b
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/glider_normal.png
Binary files differ
diff --git a/examples/Making_Activities/Using_PyGame/Demoiselle.activity/po/Demoiselle.pot b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/po/Demoiselle.pot
new file mode 100644
index 0000000..e50ab51
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/po/Demoiselle.pot
@@ -0,0 +1,3 @@
+#: activity/activity.info:2
+msgid "Demoiselle"
+msgstr ""
diff --git a/examples/Making_Activities/Using_PyGame/Demoiselle.activity/setup.py b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/setup.py
new file mode 100755
index 0000000..ebc201d
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.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/Making_Activities/Using_PyGame/Demoiselle.activity/sky.jpg b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/sky.jpg
new file mode 100644
index 0000000..a92cf6c
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/sky.jpg
Binary files differ
diff --git a/examples/Making_Activities/Using_PyGame/Demoiselle.activity/sugargame/__init__.py b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/sugargame/__init__.py
new file mode 100644
index 0000000..7e49527
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/sugargame/__init__.py
@@ -0,0 +1 @@
+__version__ = '1.0'
diff --git a/examples/Making_Activities/Using_PyGame/Demoiselle.activity/sugargame/canvas.py b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/sugargame/canvas.py
new file mode 100644
index 0000000..cf99a13
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/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/Making_Activities/Using_PyGame/Demoiselle.activity/sugargame/event.py b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/sugargame/event.py
new file mode 100644
index 0000000..52ca4ab
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/Demoiselle.activity/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/Making_Activities/Using_PyGame/DemoiselleActivity.py b/examples/Making_Activities/Using_PyGame/DemoiselleActivity.py
new file mode 100644
index 0000000..2c6c99f
--- /dev/null
+++ b/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/Making_Activities/Using_PyGame/MANIFEST b/examples/Making_Activities/Using_PyGame/MANIFEST
new file mode 100644
index 0000000..42b54df
--- /dev/null
+++ b/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/Making_Activities/Using_PyGame/activity/activity.info b/examples/Making_Activities/Using_PyGame/activity/activity.info
new file mode 100644
index 0000000..2767e12
--- /dev/null
+++ b/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/Making_Activities/Using_PyGame/activity/activity.info~ b/examples/Making_Activities/Using_PyGame/activity/activity.info~
new file mode 100644
index 0000000..137333f
--- /dev/null
+++ b/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/Making_Activities/Using_PyGame/activity/read-etexts.svg b/examples/Making_Activities/Using_PyGame/activity/read-etexts.svg
new file mode 100644
index 0000000..5682ec8
--- /dev/null
+++ b/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/Making_Activities/Using_PyGame/demoiselle.png b/examples/Making_Activities/Using_PyGame/demoiselle.png
new file mode 100644
index 0000000..37db411
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/demoiselle.png
Binary files differ
diff --git a/examples/Making_Activities/Using_PyGame/demoiselle.py b/examples/Making_Activities/Using_PyGame/demoiselle.py
new file mode 100755
index 0000000..93817e1
--- /dev/null
+++ b/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/Making_Activities/Using_PyGame/demoiselle2.py b/examples/Making_Activities/Using_PyGame/demoiselle2.py
new file mode 100755
index 0000000..46656ca
--- /dev/null
+++ b/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/Making_Activities/Using_PyGame/glider_hit.png b/examples/Making_Activities/Using_PyGame/glider_hit.png
new file mode 100644
index 0000000..49f0a8c
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/glider_hit.png
Binary files differ
diff --git a/examples/Making_Activities/Using_PyGame/glider_normal.png b/examples/Making_Activities/Using_PyGame/glider_normal.png
new file mode 100644
index 0000000..8fa812b
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/glider_normal.png
Binary files differ
diff --git a/examples/Making_Activities/Using_PyGame/po/Demoiselle.pot b/examples/Making_Activities/Using_PyGame/po/Demoiselle.pot
new file mode 100644
index 0000000..e50ab51
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/po/Demoiselle.pot
@@ -0,0 +1,3 @@
+#: activity/activity.info:2
+msgid "Demoiselle"
+msgstr ""
diff --git a/examples/Making_Activities/Using_PyGame/setup.py b/examples/Making_Activities/Using_PyGame/setup.py
new file mode 100755
index 0000000..ebc201d
--- /dev/null
+++ b/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/Making_Activities/Using_PyGame/sky.jpg b/examples/Making_Activities/Using_PyGame/sky.jpg
new file mode 100644
index 0000000..a92cf6c
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/sky.jpg
Binary files differ
diff --git a/examples/Making_Activities/Using_PyGame/sugargame/__init__.py b/examples/Making_Activities/Using_PyGame/sugargame/__init__.py
new file mode 100644
index 0000000..7e49527
--- /dev/null
+++ b/examples/Making_Activities/Using_PyGame/sugargame/__init__.py
@@ -0,0 +1 @@
+__version__ = '1.0'
diff --git a/examples/Making_Activities/Using_PyGame/sugargame/canvas.py b/examples/Making_Activities/Using_PyGame/sugargame/canvas.py
new file mode 100644
index 0000000..cf99a13
--- /dev/null
+++ b/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/Making_Activities/Using_PyGame/sugargame/event.py b/examples/Making_Activities/Using_PyGame/sugargame/event.py
new file mode 100644
index 0000000..52ca4ab
--- /dev/null
+++ b/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