PyGTK/Hello World with Glade and Sweettepache: Difference between revisions
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()