PyGTK/Hello World with Glade and Sweettepache: Difference between revisions

From OLPC
Jump to navigation Jump to search
No edit summary
No edit summary
Line 147: Line 147:
toolbox.show()
toolbox.show()
==The sweettepache program source==
==The sweettepache program source==
[[Sweettepache.py source]]
<pre>
#!/usr/bin/env python

# sweettepache - a modification for OLPC Sugar environment

# tepache
# A code sketcher for python that uses pygtk, glade and SimpleGladeApp.py
# Copyright (C) 2004 Sandino Flores Moreno
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA

import os
import sys
import re
import codecs
import tokenize
import shutil
import time
import inspect
import optparse
import xml.sax
from xml.sax._exceptions import SAXParseException

if not hasattr(__builtins__, "set"):
from sets import Set as set

__version__ = "1.1"
__autor__ = 'Sandino "tigrux" Flores-Moreno'
__WIN32__ = sys.platform.startswith("win")

gnuwin32_home = "http://gnuwin32.sourceforge.net/packages.html"
pywin32_home = "http://sourceforge.net/projects/pywin32"


Name_re = re.compile("(%s)" % tokenize.Name)
ClassName_re = re.compile("([a-zA-Z0-9]+)")
InstanceName_re = re.compile("([a-z])([A-Z])")
Comment_re = re.compile(r"#-- (%s)\.(%s) (\{|\})" % (2*(tokenize.Name,)))

def normalize_symbol(base):
return "_".join(Name_re.findall(base))

def capitalize_symbol(base):
base = normalize_symbol(base)
base_pieces = [piece.capitalize() for piece in ClassName_re.findall(base)]
return "".join(base_pieces)

def uncapitalize_symbol(base):
def action(m):
groups = m.groups()
return "%s_%s" % (groups[0], groups[1].lower())
base = normalize_symbol(base)
base = base[0].lower() + base[1:]
return InstanceName_re.sub(action, base)

def printerr(s):
print >> sys.stderr, str(s)


class NotGladeDocumentException(SAXParseException):

def __init__(self, glade_writer):
strerror = "Not a glade-2 document"
SAXParseException.__init__(
self, strerror, None,
glade_writer.sax_parser)


class SimpleGladeCodeWriter(xml.sax.handler.ContentHandler):

def __init__(self, glade_file):
self.code = ""
self.roots_list = []
self.widgets_stack = []
self.tags_stack = []
self.creation_functions = []
self.callbacks = []
self.members = dict(inspect.getmembers(self))

self.parent_is_creation_function = False
self.parent_is_object = False
self.parent_is_program_name = False
self.requires_gnome = False

self.glade_file = glade_file
self.input_dir, self.input_file = os.path.split(glade_file)
base = os.path.splitext(self.input_file)[0]
self.gladep_file = os.path.join(self.input_dir, base) + ".gladep"
self.module = normalize_symbol(base)
self.output_file = os.path.join(self.input_dir, self.module) + ".py"

self.sax_parser = xml.sax.make_parser()
self.sax_parser.setFeature(xml.sax.handler.feature_external_ges, False)
self.sax_parser.setContentHandler(self)

self.data = {}
self.data["glade"] = self.input_file
self.data["module"] = self.output_file
self.data["date"] = time.asctime()
self.data["app_name"] = self.module.lower()
self.data["app_version"] = "0.0.1"
self.indent = 4 * " "

def write(self, output_file=None):
global options, status
module_code = ""
if output_file:
self.data["module"] = self.output_file = output_file

self.data["t"] = self.indent
if not self.parse():
return False

module_code += header_format % self.data
if self.requires_gnome:
module_code += import_gnome_format % self.data
module_code += app_format % self.data
module_code += self.code

module_code += main_format % self.data
if self.requires_gnome:
module_code += gnome_init_format % self.data

for root in self.roots_list:
self.data["class"] = capitalize_symbol(root)
self.data["instance"] = uncapitalize_symbol(root)
module_code += instance_format % self.data

self.data["root"] = uncapitalize_symbol(self.roots_list[0])
module_code += run_format % self.data
try:
self.output = codecs.open(self.output_file, "w", "utf-8")
self.output.write(module_code)
self.output.close()
except IOError, e:
printerr(e)
return False
if not options.no_sugar:

activity_code = sugar_activity_format % self.data
activity_file = self.output_file[:-3] + 'Activity.py'
print activity_file

try:
self.output = codecs.open(activity_file, "w", "utf-8")
self.output.write(activity_code)
self.output.close()
except IOError, e:
printerr(e)
return False

if not (options.no_bundle or options.no_sugar):
#write setup.py format
setup_code = setup_format % self.data
try:
self.output = codecs.open("setup.py", "w", "utf-8")
self.output.write(setup_code)
self.output.close()
except IOError, e:
printerr(e)
return False

#mkdir nameActivity.activity/activity
os.makedirs('activity')
os.chdir("activity")

#write nameActivity.activity/activity/activity-name.svg
icon_code = icon_format
icon_file = 'activity-' + self.output_file[:-3] + '.svg'
try:
self.output = codecs.open(icon_file, "w", "utf-8")
self.output.write(icon_code)
self.output.close()

except IOError, e:
printerr(e)
return False

#write nameActivity.activity/activity/activity.info
info_code = activity_info_format %self.data
info_file = 'activity.info'
try:
self.output = codecs.open(info_file, "w", "utf-8")
self.output.write(info_code)
self.output.close()
except IOError, e:
printerr(e)
return False

os.chdir(os.pardir)

return True

def parse(self):
if os.path.isfile(self.gladep_file):
try:
gladep = open(self.gladep_file, "r")
self.sax_parser.parse(gladep)
except SAXParseException, e:
printerr("Error parsing project file:")
printerr(e)
except IOError, e:
printerr(e)

try:
glade = open(self.glade_file, "r")
self.sax_parser.parse(glade)
except SAXParseException, e:
printerr("Error parsing document:")
printerr(e)
return False
except IOError, e:
printerr(e)
return False

return True

def startElement(self, name, attrs):
self.tags_stack.append(name)
if self.parent_is_object:
return
handler = self.members.get("start_%s" % name)
if callable(handler):
handler(name, attrs)

def endElement(self, name):
handler = self.members.get("end_%s" % name)
if callable(handler):
handler(name)
if not self.tags_stack:
raise NotGladeDocumentException(self)
self.tags_stack.pop()

def characters(self, content):
content = content.strip()
name = self.tags_stack[-1]
handler = self.members.get("characters_%s" % name)
if callable(handler):
handler(content)

def characters_creation_function(self, content):
if not self.widgets_stack:
raise NotGladeDocumentException(self)
handler = content
if handler not in self.creation_functions:
self.data["handler"] = handler
self.code += creation_format % self.data
self.creation_functions.append(handler)

def characters_program_name(self, content):
self.data["app_name"] = content

def start_object(self, name, attrs):
self.parent_is_object = True

def end_object(self, name):
self.parent_is_object = False

def start_widget(self, name, attrs):
widget_id = attrs.get("id")
api_widget_id = widget_id.split(":")[-1]
widget_class = attrs.get("class")
if not widget_id or not widget_class:
raise NotGladeDocumentException(self)
if not self.widgets_stack:
self.creation_functions = []
self.callbacks = []
class_name = capitalize_symbol(api_widget_id)
self.data["class"] = class_name
self.data["root"] = widget_id
self.roots_list.append(api_widget_id)
self.code += class_format % self.data
self.widgets_stack.append(widget_id)

def end_widget(self, name):
if not self.widgets_stack:
raise NotGladeDocumentException(self)
self.widgets_stack.pop()

def start_signal(self, name, attrs):
if not self.widgets_stack:
raise NotGladeDocumentException(self)
widget = self.widgets_stack[-1]
signal_object = attrs.get("object")
if signal_object:
return
handler = attrs.get("handler")
if not handler:
raise NotGladeDocumentException(self)
if handler.startswith("gtk_"):
return
signal = attrs.get("name")
if not signal:
raise NotGladeDocumentException(self)
self.data["widget"] = widget
self.data["signal"] = signal
self.data["handler"]= handler
if handler not in self.callbacks:
self.code += callback_format % self.data
self.callbacks.append(handler)

def start_property(self, name, attrs):
if not self.widgets_stack:
raise NotGladeDocumentException(self)
widget = self.widgets_stack[-1]
prop_name = attrs.get("name")
if not prop_name:
raise NotGladeDocumentException(self)
if prop_name == "creation_function":
self.tags_stack.append("creation_function")

def end_property(self, name):
if self.tags_stack[-1] == "creation_function":
self.tags_stack.pop()

def start_requires(self, name, attrs):
lib = attrs.get("lib")
if lib == "gnome":
self.requires_gnome = True

def start_program_name(self, name, attrs):
self.parent_is_program_name = True

def end_program_name(self, name):
self.parent_is_program_name = False

def get_callbacks_from_code(code_filename):
class_opened_methods_l = []
classes_l = []
class_methods_d = {}

for line in open(code_filename):
found_all = Comment_re.findall(line)
if found_all:
(found,) = found_all
class_method = found[0:2]
curl = found[2]
if curl == "{":
class_opened_methods_l.append(class_method)
elif curl == "}":
if class_method in class_opened_methods_l:
class_s, method_s = class_method
if not class_s in classes_l:
classes_l.append(class_s)
class_methods_d[class_s] = set()
class_methods_d[class_s].add(method_s)

classes_methods_l = [
(class_s, class_methods_d[class_s]) for class_s in classes_l
]
return classes_methods_l

def get_renamed_symbols(orig_callbacks, new_callbacks):
renamed_symbols = {}
callbacks_pairs = zip(orig_callbacks, new_callbacks)
for orig_callback, new_callback in callbacks_pairs:
orig_class, orig_methods = orig_callback
new_class, new_methods = new_callback
if orig_class != new_class:
if orig_methods.issubset(new_methods):
cap_orig_class = capitalize_symbol(orig_class)
uncap_orig_class = uncapitalize_symbol(orig_class)

cap_new_class = capitalize_symbol(new_class)
uncap_new_class = uncapitalize_symbol(new_class)

renamed_symbols[cap_orig_class] = cap_new_class
renamed_symbols[uncap_orig_class] = uncap_new_class
return renamed_symbols

def diff_apply_renamed_symbols(diff_file, renamed_widgets):
def action(m):
orig_symbol = m.group(0)
if orig_symbol in renamed_widgets:
renamed_symbol = renamed_widgets[orig_symbol]
return renamed_symbol
else:
return orig_symbol
diff_file_content = open(diff_file).read()
new_diff_file_content = Name_re.sub(action, diff_file_content)
open(diff_file, "w").write(new_diff_file_content)


def normalize_indentation(source_filename):
source_data = open(source_filename).read()
normalized_source_data = source_data.expandtabs(4)
if normalized_source_data != source_data:
print "Normalizing indentation of", source_filename
open(source_filename, "w").write(normalized_source_data)


def get_binaries_path():
binaries_path_list = os.environ["PATH"].split(os.pathsep)
if __WIN32__:
try:
import win32con
import win32api
import pywintypes
gnu_path = ""
try:
winreg_key = win32con.HKEY_LOCAL_MACHINE
winreg_subkey = "SOFTWARE\\GnuWin32"
h = win32api.RegOpenKey(winreg_key, winreg_subkey)
gnu_path = win32api.RegQueryValueEx(h, "InstallPath")[0]
win32api.RegCloseKey(h)
gnu_path_bin = os.path.join(gnu_path, "bin")
binaries_path_list.insert(0, gnu_path_bin)
except pywintypes.error, e:
printerr("You haven't installed any GnuWin32 program.")
except ImportError:
printerr("I can't look for programs in the win32 registry")
printerr("The pywin32 extension should be installed")
printerr("Download it from %s\n" % pywin32_home)
return binaries_path_list

def get_programs_paths(programs_list):
if __WIN32__:
exe_ext = ".exe"
else:
exe_ext = ""
path_list = get_binaries_path()
programs_paths = [None,]*len(programs_list)
for i,program in enumerate(programs_list):
for path in path_list:
program_path = os.path.join(path, program) + exe_ext
if os.path.isfile(program_path):
if " " in program_path:
program_path = '"%s"' % program_path
programs_paths[i] = program_path
return programs_paths

def get_required_programs():
program_names = ["diff", "patch"]
package_names = ["diffutils", "patch"]

programs_paths = get_programs_paths(program_names)
for i,program_path in enumerate(programs_paths):
if not program_path:
program, package = program_names[i], package_names[i]
printerr("Required program %s could not be found" % program)
printerr("Is the package %s installed?" % package)
if __WIN32__:
printerr("Download it from %s" % gnuwin32_home)
return None
return programs_paths

def get_options_status():
usage = "usage: %prog [options] [GLADE_FILE] [OUTPUT_FILE]"
description = "write a sketch of python code from a glade file."
status = True
version = "1.0"
parser = optparse.OptionParser(usage=usage, version=version,
description=description)
parser.add_option("-b", "--no-bundle", dest = "no_bundle",
action="store_true",
help="Do not create the activity bundle")
parser.add_option("-g", "--glade", dest="glade_file",
help="file to parse")
parser.add_option("-o", "--output", dest="output_file",
help="file to write the sketch of the code")
parser.add_option("-n", "--no-helper", dest="no_helper",
action="store_true",
help="Do not write the helper module")
parser.add_option('-s', "--without-sugar", dest = "no_sugar",
action="store_true",
help="No sweetening needed")
parser.add_option("-t", "--use-tabs", dest="use_tabs",
action="store_true",
help="\
Use tabs instead of 4 spaces for indenting. Discouraged according to PEP-8.")

(options, args) = parser.parse_args()
if not options.glade_file:
if len(args) > 0:
options.glade_file = args[0]
else:
status = False
parser.print_help()
if not options.output_file:
if len(args) > 1:
options.output_file = args[1]
return options, status

def main():
#programs_paths = get_required_programs()
#if not programs_paths:
# return -1
#diff_bin, patch_bin = programs_paths
global options, status

options, status = get_options_status()
if not status:
return -1
code_writer = SimpleGladeCodeWriter(options.glade_file)
if not options.output_file:
output_file = code_writer.output_file
else:
output_file = options.output_file
output_file_orig = output_file + ".orig"
output_file_bak = output_file + ".bak"
short_f = os.path.basename(output_file)
short_f_orig = short_f + ".orig"
short_f_bak = short_f + ".bak"
helper_module = os.path.join(code_writer.input_dir, "SimpleGladeApp.py")
diff_file = "custom.diff"

exists_output_file = os.path.exists(output_file)
exists_output_file_orig = os.path.exists(output_file_orig)

if exists_output_file:
#back up the original
shutil.copy(output_file, output_file_bak)

if not exists_output_file_orig and exists_output_file:
shutil.copy(output_file, output_file_orig)
exists_output_file_orig = True

if options.use_tabs:
code_writer.indent = "\t"
if exists_output_file_orig and exists_output_file:
if not options.use_tabs:
normalize_indentation(output_file_orig)
normalize_indentation(output_file)
if not code_writer.write(output_file):
os.remove(diff_file)
return -1
diff_command = "%s -U3 %s %s > %s"
diff_bin = "diff"
diff_args = (diff_bin, output_file, output_file_orig, diff_file)
os.system(diff_command % diff_args)
orig_callbacks = get_callbacks_from_code(output_file_orig)
callbacks = get_callbacks_from_code(output_file)
renamed_symbols = get_renamed_symbols(orig_callbacks, callbacks)
if renamed_symbols:
diff_apply_renamed_symbols(diff_file, renamed_symbols)
patch_command = "%s -fp0 %s < %s"
patch_bin = "patch"
patch_args = (patch_bin, output_file, diff_file)
if os.system(patch_command % patch_args):
os.remove(diff_file)
return -1
os.remove(diff_file)
else:
if not code_writer.write(output_file):
return -1

os.chmod(output_file, 0755)
if not options.no_helper and not os.path.isfile(helper_module):
open(helper_module, "w").write(SimpleGladeApp_content)
print "written file", output_file
return 0

header_format = """\

#!/usr/bin/env python
# -*- coding: UTF8 -*-

# Python module %(module)s
# Autogenerated from %(glade)s
# Generated on %(date)s

# Warning: Do not modify any context comment such as #--
# They are required to keep user's code

import os

import gtk
"""

app_format = """\

from SimpleGladeApp import SimpleGladeApp
from SimpleGladeApp import bindtextdomain

app_name = "%(app_name)s"
app_version = "%(app_version)s"

glade_dir = ""
locale_dir = ""

bindtextdomain(app_name, locale_dir)

"""

import_gnome_format = """\
import gnome
"""

class_format = """\

class %(class)s(SimpleGladeApp):

%(t)sdef __init__(self, path="%(glade)s",
%(t)s root="%(root)s",
%(t)s domain=app_name, **kwargs):

%(t)s%(t)spath = os.path.join(glade_dir, path)
%(t)s%(t)sSimpleGladeApp.__init__(self, path, root, domain, **kwargs)

%(t)s#-- %(class)s.new {
%(t)sdef new(self):
%(t)s%(t)sprint "A new %%s has been created" %% self.__class__.__name__
%(t)s#-- %(class)s.new }

%(t)s#-- %(class)s custom methods {
%(t)s# Write your own methods here
%(t)s#-- %(class)s custom methods }

"""

callback_format = """\
%(t)s#-- %(class)s.%(handler)s {
%(t)sdef %(handler)s(self, widget, *args):
%(t)s%(t)sprint "%(handler)s called with self.%%s" %% widget.get_name()
%(t)s#-- %(class)s.%(handler)s }

"""

creation_format = """\
%(t)s#-- %(class)s.%(handler)s {
%(t)sdef %(handler)s(self, str1, str2, int1, int2):
%(t)s%(t)swidget = gtk.Label("%(handler)s")
%(t)s%(t)swidget.show_all()
%(t)s%(t)sreturn widget
%(t)s#-- %(class)s.%(handler)s }

"""

main_format = """\

#-- main {

def main():
"""

gnome_init_format = """\
%(t)sgnome.program_init("%(app_name)s", "%(app_version)s")
"""

instance_format = """\
%(t)s%(instance)s = %(class)s()
%(t)swidget = %(instance)s.get_widget('%(root)s')
%(t)swidget.connect("destroy", lambda w: gtk.main_quit())
%(t)swidget.show()
"""

run_format = """\

%(t)s%(root)s.run()

if __name__ == "__main__":
%(t)smain()

#-- main }
"""

sugar_activity_format= """\
import %(app_name)s

from sugar.activity import activity

class %(app_name)sActivity(activity.Activity):
def __init__(self, handle):
activity.Activity.__init__(self, handle)

self.testname = '%(app_name)s'
self.set_title(self.testname)
glade = %(app_name)s.%(class)s(root='vbox1')
self.vbox = glade.get_widget('vbox1')
widgets = self.get_children()
widget = widgets[0]
widget.pack_start(self.vbox)

toolbox = activity.ActivityToolbox(self)
self.set_toolbox(toolbox)
toolbox.show()
"""

setup_format= """\
#!/usr/bin/env python
from sugar.activity import bundlebuilder
if __name__ == "__main__":
bundlebuilder.start("%(app_name)sActivity")
"""

icon_format= """\
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
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="50"
height="50"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.45.1"
sodipodi:docname="activity-gui.svg"
sodipodi:docbase="/home/tony/Documents/Activities/GUIActivity.activity/activity"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<metadata
id="metadata9">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs7" />
<sodipodi:namedview
inkscape:window-height="627"
inkscape:window-width="910"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
guidetolerance="10.0"
gridtolerance="10.0"
objecttolerance="10.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base"
inkscape:zoom="8.14"
inkscape:cx="25"
inkscape:cy="25"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:current-layer="svg2" />
<rect
x="0.26289925"
y="2.3513515"
width="48"
height="48"
style="fill:#ffffff;stroke:#000000;stroke-width:2"
id="rect4" />
<text
xml:space="preserve"
style="font-size:8px;font-style:normal;font-weight:normal;text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
x="8.7223587"
y="12.776413"
id="text2161"><tspan
sodipodi:role="line"
id="tspan2163"
x="8.7223587"
y="12.776413" /></text>
<text
xml:space="preserve"
style="font-size:14px;font-style:normal;font-weight:normal;text-align:start;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
x="10.687961"
y="3.6855037"
id="text2165"><tspan
sodipodi:role="line"
x="10.687961"
y="3.6855037"
id="tspan2169"></tspan><tspan
sodipodi:role="line"
x="10.687961"
y="21.185504"
id="tspan2162">Test</tspan><tspan
sodipodi:role="line"
x="10.687961"
y="38.685504"
id="tspan2327">GUI</tspan></text>
</svg>
"""

activity_info_format = """\
[Activity]
name = %(app_name)s
service_name = org.laptop.%(app_name)sActivity
class = %(app_name)sActivity.%(app_name)sActivity
exec = sugar-activity %(app_name)sActivity.%(app_name)sActivity
icon = activity-%(app_name)s
activity_version = 1
show_launcher = yes
"""

SimpleGladeApp_content = '''\
"""
SimpleGladeApp.py
Module that provides an object oriented abstraction to pygtk and libglade.
Copyright (C) 2004 Sandino Flores Moreno
"""

# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA

import os
import sys
import re

import tokenize
import gtk
import gtk.glade
import weakref
import inspect

__version__ = "1.0"
__author__ = 'Sandino "tigrux" Flores-Moreno'

def bindtextdomain(app_name, locale_dir=None):
"""
Bind the domain represented by app_name to the locale directory locale_dir.
It has the effect of loading translations, enabling applications for different
languages.

app_name:
a domain to look for translations, tipically the name of an application.

locale_dir:
a directory with locales like locale_dir/lang_isocode/LC_MESSAGES/app_name.mo
If omitted or None, then the current binding for app_name is used.
"""
try:
import locale
import gettext
locale.setlocale(locale.LC_ALL, "")
gtk.glade.bindtextdomain(app_name, locale_dir)
gettext.install(app_name, locale_dir, unicode=1)
except (IOError,locale.Error), e:
print "Warning", app_name, e
__builtins__.__dict__["_"] = lambda x : x


class SimpleGladeApp:

def __init__(self, path, root=None, domain=None, **kwargs):
"""
Load a glade file specified by glade_filename, using root as
root widget and domain as the domain for translations.

If it receives extra named arguments (argname=value), then they are used
as attributes of the instance.

path:
path to a glade filename.
If glade_filename cannot be found, then it will be searched in the
same directory of the program (sys.argv[0])

root:
the name of the widget that is the root of the user interface,
usually a window or dialog (a top level widget).
If None or ommited, the full user interface is loaded.

domain:
A domain to use for loading translations.
If None or ommited, no translation is loaded.

**kwargs:
a dictionary representing the named extra arguments.
It is useful to set attributes of new instances, for example:
glade_app = SimpleGladeApp("ui.glade", foo="some value", bar="another value")
sets two attributes (foo and bar) to glade_app.
"""
if os.path.isfile(path):
self.glade_path = path
else:
glade_dir = os.path.dirname( sys.argv[0] )
self.glade_path = os.path.join(glade_dir, path)
for key, value in kwargs.items():
try:
setattr(self, key, weakref.proxy(value) )
except TypeError:
setattr(self, key, value)
self.glade = None
self.install_custom_handler(self.custom_handler)
self.glade = self.create_glade(self.glade_path, root, domain)
if root:
self.main_widget = self.get_widget(root)
else:
self.main_widget = None
self.normalize_names()
self.add_callbacks(self)
self.new()

def __repr__(self):
class_name = self.__class__.__name__
if self.main_widget:
root = gtk.Widget.get_name(self.main_widget)
repr = '%s(path="%s", root="%s")' % (class_name, self.glade_path, root)
else:
repr = '%s(path="%s")' % (class_name, self.glade_path)
return repr

def new(self):
"""
Method called when the user interface is loaded and ready to be used.
At this moment, the widgets are loaded and can be refered as self.widget_name
"""
pass

def add_callbacks(self, callbacks_proxy):
"""
It uses the methods of callbacks_proxy as callbacks.
The callbacks are specified by using:
Properties window -> Signals tab
in glade-2 (or any other gui designer like gazpacho).

Methods of classes inheriting from SimpleGladeApp are used as
callbacks automatically.

callbacks_proxy:
an instance with methods as code of callbacks.
It means it has methods like on_button1_clicked, on_entry1_activate, etc.
"""
self.glade.signal_autoconnect(callbacks_proxy)

def normalize_names(self):
"""
It is internally used to normalize the name of the widgets.
It means a widget named foo:vbox-dialog in glade
is refered self.vbox_dialog in the code.

It also sets a data "prefixes" with the list of
prefixes a widget has for each widget.
"""
for widget in self.get_widgets():
widget_name = gtk.Widget.get_name(widget)
prefixes_name_l = widget_name.split(":")
prefixes = prefixes_name_l[ : -1]
widget_api_name = prefixes_name_l[-1]
widget_api_name = "_".join( re.findall(tokenize.Name, widget_api_name) )
gtk.Widget.set_name(widget, widget_api_name)
if hasattr(self, widget_api_name):
raise AttributeError("instance %s already has an attribute %s" % (self,widget_api_name))
else:
setattr(self, widget_api_name, widget)
if prefixes:
gtk.Widget.set_data(widget, "prefixes", prefixes)

def add_prefix_actions(self, prefix_actions_proxy):
"""
By using a gui designer (glade-2, gazpacho, etc)
widgets can have a prefix in theirs names
like foo:entry1 or foo:label3
It means entry1 and label3 has a prefix action named foo.

Then, prefix_actions_proxy must have a method named prefix_foo which
is called everytime a widget with prefix foo is found, using the found widget
as argument.

prefix_actions_proxy:
An instance with methods as prefix actions.
It means it has methods like prefix_foo, prefix_bar, etc.
"""
prefix_s = "prefix_"
prefix_pos = len(prefix_s)

is_method = lambda t : callable( t[1] )
is_prefix_action = lambda t : t[0].startswith(prefix_s)
drop_prefix = lambda (k,w): (k[prefix_pos:],w)

members_t = inspect.getmembers(prefix_actions_proxy)
methods_t = filter(is_method, members_t)
prefix_actions_t = filter(is_prefix_action, methods_t)
prefix_actions_d = dict( map(drop_prefix, prefix_actions_t) )

for widget in self.get_widgets():
prefixes = gtk.Widget.get_data(widget, "prefixes")
if prefixes:
for prefix in prefixes:
if prefix in prefix_actions_d:
prefix_action = prefix_actions_d[prefix]
prefix_action(widget)

def custom_handler(self,
glade, function_name, widget_name,
str1, str2, int1, int2):
"""
Generic handler for creating custom widgets, internally used to
enable custom widgets (custom widgets of glade).

The custom widgets have a creation function specified in design time.
Those creation functions are always called with str1,str2,int1,int2 as
arguments, that are values specified in design time.

Methods of classes inheriting from SimpleGladeApp are used as
creation functions automatically.

If a custom widget has create_foo as creation function, then the
method named create_foo is called with str1,str2,int1,int2 as arguments.
"""
try:
handler = getattr(self, function_name)
return handler(str1, str2, int1, int2)
except AttributeError:
return None

def gtk_widget_show(self, widget, *args):
"""
Predefined callback.
The widget is showed.
Equivalent to widget.show()
"""
widget.show()

def gtk_widget_hide(self, widget, *args):
"""
Predefined callback.
The widget is hidden.
Equivalent to widget.hide()
"""
widget.hide()

def gtk_widget_grab_focus(self, widget, *args):
"""
Predefined callback.
The widget grabs the focus.
Equivalent to widget.grab_focus()
"""
widget.grab_focus()

def gtk_widget_destroy(self, widget, *args):
"""
Predefined callback.
The widget is destroyed.
Equivalent to widget.destroy()
"""
widget.destroy()

def gtk_window_activate_default(self, window, *args):
"""
Predefined callback.
The default widget of the window is activated.
Equivalent to window.activate_default()
"""
widget.activate_default()

def gtk_true(self, *args):
"""
Predefined callback.
Equivalent to return True in a callback.
Useful for stopping propagation of signals.
"""
return True

def gtk_false(self, *args):
"""
Predefined callback.
Equivalent to return False in a callback.
"""
return False

def gtk_main_quit(self, *args):
"""
Predefined callback.
Equivalent to self.quit()
"""
self.quit()

def main(self):
"""
Starts the main loop of processing events.
The default implementation calls gtk.main()

Useful for applications that needs a non gtk main loop.
For example, applications based on gstreamer needs to override
this method with gst.main()

Do not directly call this method in your programs.
Use the method run() instead.
"""
gtk.main()

def quit(self):
"""
Quit processing events.
The default implementation calls gtk.main_quit()
Useful for applications that needs a non gtk main loop.
For example, applications based on gstreamer needs to override
this method with gst.main_quit()
"""
gtk.main_quit()

def run(self):
"""
Starts the main loop of processing events checking for Control-C.

The default implementation checks wheter a Control-C is pressed,
then calls on_keyboard_interrupt().

Use this method for starting programs.
"""
try:
self.main()
except KeyboardInterrupt:
self.on_keyboard_interrupt()

def on_keyboard_interrupt(self):
"""
This method is called by the default implementation of run()
after a program is finished by pressing Control-C.
"""
pass

def install_custom_handler(self, custom_handler):
gtk.glade.set_custom_handler(custom_handler)

def create_glade(self, glade_path, root, domain):
return gtk.glade.XML(self.glade_path, root, domain)

def get_widget(self, widget_name):
return self.glade.get_widget(widget_name)

def get_widgets(self):
return self.glade.get_widget_prefix("")
'''

if __name__ == "__main__":
exit_code = main()
sys.exit(exit_code)
</pre>
==Changes made to tepache to produce sweettepache==
==Changes made to tepache to produce sweettepache==
==References==
==References==

Revision as of 21:57, 20 February 2008

PyGTK/Hello World with Glade and Sweettepache

The PyGTK/Hello World Tutorial shows how to create the python program Hello World as a Sugar Activity. PyGTK/Hello World with Glade shows how to build the same activity using Glade. Glade.py provides a visual editor for designing a program's GUI (graphical user interface). The output from Glade is a file 'name.glade' where name is the name of the activity Tepache 'tepache.py' is a python program which processes the glade file to produce a skeleton module name.py. This module contains the has the code to connect with the GUI. The specific application logic is then added to complete the module.

The program sweettepache.py is a modification of tepache which, in addition to name.py, produces a nameActivity.py module for Sugar, and produces the files and folders needed to make an activity bundle. The result is a module name.py which can be tested on Ubuntu (or possibly other platform). When the module works correctly on Ubuntu, it and the activity structure can be ported unchanged to the XO.

he purpose of this page is to make sweettepache.py available and to show how it can be used to build and test sugar-compatible python code.

This project was developed on Ubuntu 7.10 and the Hello World activity was ported and tested on a G1G1 XO upgraded to release 656. An important caveat is that there has been no robust testing (after all, almost anything can work for 'Hello World').

Activity Development Process

The following is the step by step process in building and testing a Sugar Activity using Sweettepache.

Design the GUI

See PyGTK/Hello World with Glade for a description of this step. Suppose the name of your activity is 'cool'. Make a folder, cool.activity. Save the glade file in that folder as cool.glade The important point is that when the glade file is saved, its name 'cool' will be used by sweettepache as the name of the activity. Also, the folder cool.activity will be the top-level of the Sugar bundle.

Create the skeleton module

  • Copy sweettepache.py to cool.activity
  • Open a Terminal and 'cd' to the folder cool.activity
  • Execute the command: python sweettepache.py cool.glade

Instantly you have an activity bundle complete with icon!

Use the command: sweettepache.py --help to see the available flags. Two flags have been added:

  • -s makes sweettepache work identically to tepache (no sweetening).
  • -i flag requests the generic icon (activity-cool.svg) not not be written.

Only in demos do programs work correctly the first time. In reality, you will want to change the GUI. To do this, open cool.glade in Glade, make changes, and save the modified file. Run sweettepache again. Sweettepache saves a copy of your module as cool.py.bak. It also creates a copy of the module as cool.py.orig (the only difference is that sweettepache 'normalized the indents' in cool.py.orig). Sweettepache creates a temporary diff file with the new skeleton to detect your application code. It then produces a new cool.py which incorporates your code with the modified interface to the GUI.

Test on Ubuntu

It is probably a waste of time to test a program on the XO as an Activity before it works correctly on Ubuntu. Sweettepache produces a module coolActivity.py which provides the code specific to the XO. The intention is that name.py runs on Ubuntu as is. On the XO, coolActivity.py initializes the Activity and then calls cool.py. You may need to make changes to coolActivity.py. for example, to add a button to the toolbar, Sweettepache will create a copy of coolActivity.py as coolActivity.py.bak.

Test on the XO

Currently I use a USB stick. Simply copy the name.activity folder to the stick. On the Xo, open a Terminal. Change to root (su). Copy the name.activity folder from the stick to /home/olpc/Activities. Restart Sugar (Ctl + Alt + Erase). Your activity should show up on the bottom panel. Start your activity.

If, after a reasonable time it is still 'starting', it usually means that python found a syntax or other error that prevented the program from starting. Often the explanation will be found in the activiity log. Open the log viewer (this shows the logs stored in /home/olpc/.sugar/default/logs). There should be an entry for your activity. This will show messages from python as well as the output of print statements in your code.

There are two (probably temporary) gotchas. When you open a Terminal on the Xo, the focus is on the toolbar, not the prompt. When the Terminal opens, click the mouse in the Terminal window first. The second is that there seems to be no way to 'stop' an activity that is 'starting'. It requires the CTL-ALT-Erase solution.

If the problem which prevents the activity from executing is simple, it can be fixed in the Terminal. As root (su), nano is available to edit your module in /home/olpc/Activities/name.activity.

Once the program starts, you will immediately see one of the main differences between Ubuntu and Sugar, the GUI window opens full screen on Sugar. This may well require tweaking of the Glade design to make the results more attractive on the Xo.

Technical details

Normally, glade generates the root window for an application. However, Sugar provides the toplevel window. The result is that the GUI definition from glade must be connected to the sugar window. This means that the glade file is processed on sugar beginning at the top widget below the level of the window (for example, a vbox container). This widget is then packed into an existing vbox container supplied by sugar. The details of this are apparently changing from release to release. The first tutorial (PyGTK/Hello World) didn't use Glade. The page PyGTK/Hello World with Glade explains the issue clearly. However, now that solution doesn't work because of the pre-existing vbox in the sugar window. There are, of course, no guarantees that a later release of Sugar will not break sweettepaches solution.

One additional advantage of the sweettepache strategy is that all of the details of building an activity structure can be encapsulated in a form which is easily changed. The last half of sweettepache is the 'boilerplate' code it generates. These lines are customized for a specific application by entries of the form %(class)s which produces a variable output depending on the contents of 'self.data'. The entry %(t)s produces a tab or four spaces (as needed by python). Caution: the trailing s is needed (it means produce a string).

This scheme makes it easy to modify the copy of sweettepache in the activity folder to generate specific code such as an additional button on the taskbar.

The module 'holamundo.py' produced by sweettepache

#!/usr/bin/env python
# -*- coding: UTF8 -*-
# Python module Holamundo.py
# Autogenerated from Holamundo.glade
# Generated on Wed Feb 20 12:48:26 2008
# Warning: Do not modify any context comment such as #--
# They are required to keep user's code
import os
import gtk
from SimpleGladeApp import SimpleGladeApp
from SimpleGladeApp import bindtextdomain
app_name = "holamundo"
app_version = "0.0.1"
glade_dir = ""
locale_dir = ""
bindtextdomain(app_name, locale_dir)


class Window1(SimpleGladeApp):
    def __init__(self, path="Holamundo.glade",
                 root="window1",
                 domain=app_name, **kwargs):
        path = os.path.join(glade_dir, path)
        SimpleGladeApp.__init__(self, path, root, domain, **kwargs)
    #-- Window1.new {
    def new(self):
        print "A new %s has been created" % self.__class__.__name__
    #-- Window1.new }
    #-- Window1 custom methods {
    #   Write your own methods here
    #-- Window1 custom methods }
    #-- Window1.on_boton_clicked {
    def on_boton_clicked(self, widget, *args):
        print "on_boton_clicked called with self.%s" % widget.get_name()
    #-- Window1.on_boton_clicked }
        widget = self.get_widget('texto')
        widget.set_text('Hola Mundo!')


#-- main {
def main():
    window1 = Window1()
    widget = window1.get_widget('window1')
    widget.connect("destroy", lambda w: gtk.main_quit())
    widget.show()
    window1.run()
if __name__ == "__main__":
    main()
#-- main }

The module 'holamundoActivity.py' produced by sweettepache

import holamundo
from sugar.activity import activity
class holamundoActivity(activity.Activity):
    def __init__(self, handle):
        activity.Activity.__init__(self, handle)
        self.testname = 'holamundo'
        self.set_title(self.testname)
       
        glade = holamundo.Window1(root='vbox1')
        self.vbox = glade.get_widget('vbox1')
        widgets = self.get_children()
        widget = widgets[0]
        widget.pack_start(self.vbox)

toolbox = activity.ActivityToolbox(self) self.set_toolbox(toolbox) toolbox.show()

The sweettepache program source

Sweettepache.py source

Changes made to tepache to produce sweettepache

References