Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/sugar-toolkit/src/sugar/tutorius
diff options
context:
space:
mode:
Diffstat (limited to 'sugar-toolkit/src/sugar/tutorius')
-rw-r--r--sugar-toolkit/src/sugar/tutorius/Makefile.am10
-rw-r--r--sugar-toolkit/src/sugar/tutorius/Makefile.in437
-rw-r--r--sugar-toolkit/src/sugar/tutorius/__init__.py0
-rw-r--r--sugar-toolkit/src/sugar/tutorius/actions.py152
-rw-r--r--sugar-toolkit/src/sugar/tutorius/calc.py130
-rw-r--r--sugar-toolkit/src/sugar/tutorius/core.py334
-rw-r--r--sugar-toolkit/src/sugar/tutorius/dialog.py59
-rw-r--r--sugar-toolkit/src/sugar/tutorius/dialog.pycbin0 -> 1696 bytes
-rw-r--r--sugar-toolkit/src/sugar/tutorius/dragbox.py120
-rw-r--r--sugar-toolkit/src/sugar/tutorius/dragbox.pycbin0 -> 4454 bytes
-rw-r--r--sugar-toolkit/src/sugar/tutorius/filters.py162
-rw-r--r--sugar-toolkit/src/sugar/tutorius/gtkutils.py169
-rw-r--r--sugar-toolkit/src/sugar/tutorius/overlayer.py328
-rw-r--r--sugar-toolkit/src/sugar/tutorius/overlayer.pycbin0 -> 7987 bytes
-rw-r--r--sugar-toolkit/src/sugar/tutorius/services.py68
-rw-r--r--sugar-toolkit/src/sugar/tutorius/tests/.coverage1
-rw-r--r--sugar-toolkit/src/sugar/tutorius/tests/coretests.py197
-rw-r--r--sugar-toolkit/src/sugar/tutorius/tests/coretests.pycbin0 -> 8818 bytes
-rw-r--r--sugar-toolkit/src/sugar/tutorius/tests/overlaytests.py115
-rw-r--r--sugar-toolkit/src/sugar/tutorius/tests/overlaytests.pycbin0 -> 4563 bytes
-rwxr-xr-xsugar-toolkit/src/sugar/tutorius/tests/run-tests.py12
-rw-r--r--sugar-toolkit/src/sugar/tutorius/testwin.py92
-rw-r--r--sugar-toolkit/src/sugar/tutorius/textbubble.py109
-rw-r--r--sugar-toolkit/src/sugar/tutorius/textbubble.pycbin0 -> 3976 bytes
-rw-r--r--sugar-toolkit/src/sugar/tutorius/tutorial.py162
25 files changed, 2657 insertions, 0 deletions
diff --git a/sugar-toolkit/src/sugar/tutorius/Makefile.am b/sugar-toolkit/src/sugar/tutorius/Makefile.am
new file mode 100644
index 0000000..6fd32c7
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/Makefile.am
@@ -0,0 +1,10 @@
+sugardir = $(pythondir)/sugar/tutorius
+sugar_PYTHON = \
+ __init__.py \
+ core.py \
+ dialog.py \
+ actions.py \
+ gtkutils.py \
+ filters.py \
+ services.py \
+ overlayer.py
diff --git a/sugar-toolkit/src/sugar/tutorius/Makefile.in b/sugar-toolkit/src/sugar/tutorius/Makefile.in
new file mode 100644
index 0000000..b6f8607
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/Makefile.in
@@ -0,0 +1,437 @@
+# Makefile.in generated by automake 1.10.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/sugar/tutorius
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in \
+ $(sugar_PYTHON)
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gnome-compiler-flags.m4 \
+ $(top_srcdir)/m4/intltool.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/m4/python.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_CLEAN_FILES =
+SOURCES =
+DIST_SOURCES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = `echo $$p | sed -e 's|^.*/||'`;
+am__installdirs = "$(DESTDIR)$(sugardir)"
+sugarPYTHON_INSTALL = $(INSTALL_DATA)
+py_compile = $(top_srcdir)/py-compile
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ALL_LINGUAS = @ALL_LINGUAS@
+AMTAR = @AMTAR@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CATALOGS = @CATALOGS@
+CATOBJEXT = @CATOBJEXT@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DATADIRNAME = @DATADIRNAME@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+EXT_CFLAGS = @EXT_CFLAGS@
+EXT_LIBS = @EXT_LIBS@
+FGREP = @FGREP@
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GMOFILES = @GMOFILES@
+GMSGFMT = @GMSGFMT@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INSTOBJEXT = @INSTOBJEXT@
+INTLLIBS = @INTLLIBS@
+INTLTOOL_CAVES_RULE = @INTLTOOL_CAVES_RULE@
+INTLTOOL_DESKTOP_RULE = @INTLTOOL_DESKTOP_RULE@
+INTLTOOL_DIRECTORY_RULE = @INTLTOOL_DIRECTORY_RULE@
+INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@
+INTLTOOL_KBD_RULE = @INTLTOOL_KBD_RULE@
+INTLTOOL_KEYS_RULE = @INTLTOOL_KEYS_RULE@
+INTLTOOL_MERGE = @INTLTOOL_MERGE@
+INTLTOOL_OAF_RULE = @INTLTOOL_OAF_RULE@
+INTLTOOL_PERL = @INTLTOOL_PERL@
+INTLTOOL_POLICY_RULE = @INTLTOOL_POLICY_RULE@
+INTLTOOL_PONG_RULE = @INTLTOOL_PONG_RULE@
+INTLTOOL_PROP_RULE = @INTLTOOL_PROP_RULE@
+INTLTOOL_SCHEMAS_RULE = @INTLTOOL_SCHEMAS_RULE@
+INTLTOOL_SERVER_RULE = @INTLTOOL_SERVER_RULE@
+INTLTOOL_SERVICE_RULE = @INTLTOOL_SERVICE_RULE@
+INTLTOOL_SHEET_RULE = @INTLTOOL_SHEET_RULE@
+INTLTOOL_SOUNDLIST_RULE = @INTLTOOL_SOUNDLIST_RULE@
+INTLTOOL_THEME_RULE = @INTLTOOL_THEME_RULE@
+INTLTOOL_UI_RULE = @INTLTOOL_UI_RULE@
+INTLTOOL_UPDATE = @INTLTOOL_UPDATE@
+INTLTOOL_XAM_RULE = @INTLTOOL_XAM_RULE@
+INTLTOOL_XML_NOMERGE_RULE = @INTLTOOL_XML_NOMERGE_RULE@
+INTLTOOL_XML_RULE = @INTLTOOL_XML_RULE@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+MSGFMT = @MSGFMT@
+MSGFMT_OPTS = @MSGFMT_OPTS@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+POFILES = @POFILES@
+POSUB = @POSUB@
+PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@
+PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@
+PYGTK_CODEGEN = @PYGTK_CODEGEN@
+PYGTK_DEFSDIR = @PYGTK_DEFSDIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_INCLUDES = @PYTHON_INCLUDES@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WARN_CFLAGS = @WARN_CFLAGS@
+XGETTEXT = @XGETTEXT@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_ECHO = @lt_ECHO@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+sugardir = $(pythondir)/sugar/tutorius
+sugar_PYTHON = \
+ __init__.py \
+ core.py \
+ dialog.py \
+ actions.py \
+ gtkutils.py \
+ filters.py \
+ services.py \
+ overlayer.py
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/sugar/tutorius/Makefile'; \
+ cd $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/sugar/tutorius/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-sugarPYTHON: $(sugar_PYTHON)
+ @$(NORMAL_INSTALL)
+ test -z "$(sugardir)" || $(MKDIR_P) "$(DESTDIR)$(sugardir)"
+ @list='$(sugar_PYTHON)'; dlist=''; for p in $$list; do\
+ if test -f "$$p"; then b=; else b="$(srcdir)/"; fi; \
+ if test -f $$b$$p; then \
+ f=$(am__strip_dir) \
+ dlist="$$dlist $$f"; \
+ echo " $(sugarPYTHON_INSTALL) '$$b$$p' '$(DESTDIR)$(sugardir)/$$f'"; \
+ $(sugarPYTHON_INSTALL) "$$b$$p" "$(DESTDIR)$(sugardir)/$$f"; \
+ else :; fi; \
+ done; \
+ if test -n "$$dlist"; then \
+ if test -z "$(DESTDIR)"; then \
+ PYTHON=$(PYTHON) $(py_compile) --basedir "$(sugardir)" $$dlist; \
+ else \
+ PYTHON=$(PYTHON) $(py_compile) --destdir "$(DESTDIR)" --basedir "$(sugardir)" $$dlist; \
+ fi; \
+ else :; fi
+
+uninstall-sugarPYTHON:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sugar_PYTHON)'; dlist=''; for p in $$list; do\
+ f=$(am__strip_dir) \
+ rm -f "$(DESTDIR)$(sugardir)/$$f"; \
+ rm -f "$(DESTDIR)$(sugardir)/$${f}c"; \
+ rm -f "$(DESTDIR)$(sugardir)/$${f}o"; \
+ done
+tags: TAGS
+TAGS:
+
+ctags: CTAGS
+CTAGS:
+
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+ fi; \
+ cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
+ else \
+ test -f $(distdir)/$$file \
+ || cp -p $$d/$$file $(distdir)/$$file \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile
+installdirs:
+ for dir in "$(DESTDIR)$(sugardir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+info: info-am
+
+info-am:
+
+install-data-am: install-sugarPYTHON
+
+install-dvi: install-dvi-am
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-info: install-info-am
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-ps: install-ps-am
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-sugarPYTHON
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ distclean distclean-generic distclean-libtool distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ install-sugarPYTHON installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ uninstall uninstall-am uninstall-sugarPYTHON
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/sugar-toolkit/src/sugar/tutorius/__init__.py b/sugar-toolkit/src/sugar/tutorius/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/__init__.py
diff --git a/sugar-toolkit/src/sugar/tutorius/actions.py b/sugar-toolkit/src/sugar/tutorius/actions.py
new file mode 100644
index 0000000..da8219e
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/actions.py
@@ -0,0 +1,152 @@
+# Copyright (C) 2009, Tutorius.org
+#
+# 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
+"""
+This module defines Actions that can be done and undone on a state
+"""
+
+from sugar.tutorius import gtkutils
+from dialog import TutoriusDialog
+from sugar.tutorius.services import ObjectStore
+import overlayer
+
+
+class Action(object):
+ """Base class for Actions"""
+ def __init__(self):
+ object.__init__(self)
+
+ def do(self, **kwargs):
+ """
+ Perform the action
+ """
+ raise NotImplementedError("Not implemented")
+
+ def undo(self):
+ """
+ Revert anything the action has changed
+ """
+ pass #Should raise NotImplemented?
+
+
+class OnceWrapper(object):
+ """
+ Wraps a class to perform an action once only
+
+ This ConcreteActions's do() method will only be called on the first do()
+ and the undo() will be callable after do() has been called
+ """
+ def __init__(self, action):
+ self._action = action
+ self._called = False
+ self._need_undo = False
+
+ def do(self):
+ """
+ Do the action only on the first time
+ """
+ if not self._called:
+ self._called = True
+ self._action.do()
+ self._need_undo = True
+
+ def undo(self):
+ """
+ Undo the action if it's been done
+ """
+ if self._need_undo:
+ self._action.undo()
+ self._need_undo = False
+
+class DialogMessage(Action):
+ """
+ Shows a dialog with a given text, at the given position on the screen.
+
+ @param message A string to display to the user
+ @param pos A list of the form [x, y]
+ """
+ def __init__(self, message, pos=[0,0]):
+ super(DialogMessage, self).__init__()
+ self._message = message
+ self.position = pos
+ self._dialog = None
+
+ def do(self):
+ """
+ Show the dialog
+ """
+ self._dialog = TutoriusDialog(self._message)
+ self._dialog.set_button_clicked_cb(self._dialog.close_self)
+ self._dialog.set_modal(False)
+ self._dialog.move(self.position[0], self.position[1])
+ self._dialog.show()
+
+ def undo(self):
+ """
+ Destroy the dialog
+ """
+ if self._dialog:
+ self._dialog.destroy()
+ self._dialog = None
+
+
+class BubbleMessage(Action):
+ """
+ Shows a dialog with a given text, at the given position on the screen.
+
+ @param message A string to display to the user
+ @param pos A list of the form [x, y]
+ @param speaker treeish representation of the speaking widget
+ """
+ def __init__(self, message, pos=[0,0], speaker=None, tailpos=None):
+ Action.__init__(self)
+ self._message = message
+ self.position = pos
+
+ self.overlay = None
+ self._bubble = None
+ self._speaker = None
+ self._tailpos = tailpos
+
+
+ def do(self):
+ """
+ Show the dialog
+ """
+ # get or inject overlayer
+ self.overlay = ObjectStore().activity._overlayer
+ # FIXME: subwindows, are left to overlap this. This behaviour is
+ # undesirable. subwindows (i.e. child of top level windows) should be
+ # handled either by rendering over them, or by finding different way to
+ # draw the overlay.
+
+ if not self._bubble:
+ x, y = self.position
+ # TODO: tails are relative to tailpos. They should be relative to
+ # the speaking widget. Same of the bubble position.
+ self._bubble = overlayer.TextBubble(text=self._message,
+ tailpos=self._tailpos)
+ self._bubble.show()
+ self.overlay.put(self._bubble, x, y)
+ self.overlay.queue_draw()
+
+ def undo(self):
+ """
+ Destroy the dialog
+ """
+ if self._bubble:
+ self._bubble.destroy()
+ self._bubble = None
+
diff --git a/sugar-toolkit/src/sugar/tutorius/calc.py b/sugar-toolkit/src/sugar/tutorius/calc.py
new file mode 100644
index 0000000..56ceba6
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/calc.py
@@ -0,0 +1,130 @@
+import logging
+
+import sys, os
+import pygtk
+pygtk.require20()
+import gtk
+
+import overlayer
+
+
+#import rpdb2
+#rpdb2.start_embedded_debugger("foo", True, True)
+
+class TootOriole():
+ def hello(self, widget, data=None):
+ logging.info('Hello World')
+
+ def buttonclicked(self, button, data=None):
+ evt = button.child.get_text()
+ if evt not in ("+", "-", "*", "/", "=", "."):
+ if self.newNum:
+ self.reg1 = 0
+ self.newNum = False
+ if not self.decimal:
+ self.reg1 = self.reg1*10+int(evt)
+ else:
+ self.reg1 += self.decimal*int(evt)
+ self.decimal /= 10
+ else:
+ if (evt == "."):
+ self.decimal = 0.1
+ return
+
+ if evt == "=":
+ evt = None
+
+ self.reg2 = {
+ "+": lambda a, b: a+b,
+ "-": lambda a, b: a-b,
+ "*": lambda a, b: a*b,
+ "/": lambda a, b: a/b,
+ None: lambda a, b: b
+ }[self.lastOp](self.reg2, self.reg1)
+ self.reg1 = self.reg2
+ self.newNum = True
+ self.decimal = 0
+ self.lastOp = evt
+ self.label.set_label(str(self.reg1))
+
+ def __init__(self):
+ self.reg1 = 0
+ self.reg2 = 0
+ self.lastOp = None
+ self.decimal = 0
+ self.newNum = False
+
+ print "running activity init"
+ self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ self.window.connect("delete_event", self.delete_event)
+ self.window.connect("destroy", self.destroy)
+ print "activity running"
+
+ # Creates the Toolbox. It contains the Activity Toolbar, which is the
+ # bar that appears on every Sugar window and contains essential
+ # functionalities, such as the 'Collaborate' and 'Close' buttons.
+ #toolbox = activity.ActivityToolbox(self)
+ #self.set_toolbox(toolbox)
+ #toolbox.show()
+
+ self.vbox = gtk.VBox(homogeneous=False, spacing=20)
+
+ #textbox
+ self.label = gtk.Label("0")
+ self.label.show()
+ self.vbox.pack_start(self.label, False, True, 10)
+
+ table = gtk.Table(rows=4, columns=4, homogeneous=True)
+ table.show()
+ self.vbox.pack_start(child=table, expand=True, fill=True, \
+ padding=10)
+ buttonlist = [0, "+", "-", "x", 7, 8, 9, "/", 4, 5, 6, "=", \
+ 1, 2, 3, "."]
+
+ for i in range(4):
+ for j in range(4):
+ button = gtk.Button("")
+ button.child.set_markup("<big><b>%s</b></big>" \
+ % str(buttonlist.pop(0)))
+ table.attach(button, j, j+1, i, i+1, gtk.EXPAND|gtk.FILL,
+ gtk.EXPAND|gtk.FILL, 15, 15)
+ button.connect("clicked", self.buttonclicked, None)
+ button.show()
+
+ # Set the button to be our canvas. The canvas is the main section of
+ # every Sugar Window. It fills all the area below the toolbox.
+
+
+ # The final step is to display this newly created widget.
+ self.vbox.show()
+ self.window.add(self.vbox)
+
+ self.window.show()
+
+ # proto overlap
+ # ====================================================================
+ # =================================== clip here ======================
+ # Create overlay base where all overlayed widgets will reside
+ overlayBase = overlayer.Overlayer()
+ overlayBase.inject(self.vbox)
+ bubble = overlayer.TextBubble(text="Hello,\nI'm a comma.Use me to\nrepresent decimal numbers.", speaker=button)
+ overlayBase.put(bubble, 40, 50)
+ # we do not eject the overlay, but it could be done when overlay is not
+ # needed anymore
+ # =================================== end clip =======================
+ # ====================================================================
+
+
+ def delete_event(self, widget, event, data=None):
+ print "quitting..."
+ return False
+
+ def destroy(self, widget, data=None):
+ gtk.main_quit()
+
+
+
+
+if __name__ == "__main__":
+ t = TootOriole()
+ gtk.main()
diff --git a/sugar-toolkit/src/sugar/tutorius/core.py b/sugar-toolkit/src/sugar/tutorius/core.py
new file mode 100644
index 0000000..f817ba9
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/core.py
@@ -0,0 +1,334 @@
+# Copyright (C) 2009, Tutorius.org
+# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com>
+#
+# 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
+"""
+Core
+
+This module contains the core classes for tutorius
+
+"""
+
+import gtk
+import logging
+
+from sugar.tutorius.dialog import TutoriusDialog
+from sugar.tutorius.gtkutils import find_widget
+
+logger = logging.getLogger("tutorius")
+
+class Tutorial (object):
+ """
+ Tutorial Class, used to run through the FSM.
+ """
+
+ def __init__(self, name, fsm):
+ """
+ Creates an unattached tutorial.
+ """
+ object.__init__(self)
+ self.name = name
+
+ self.state_machine = fsm
+ self.state_machine.set_tutorial(self)
+
+ self.state = None
+
+ self.handlers = []
+ self.activity = None
+ #Rest of initialisation happens when attached
+
+ def attach(self, activity):
+ """
+ Attach to a running activity
+
+ @param activity the activity to attach to
+ """
+ #For now, absolutely detach if a previous one!
+ if self.activity:
+ self.detach()
+ self.activity = activity
+ self.state_machine.set_state("INIT")
+
+ def detach(self):
+ """
+ Detach from the current activity
+ """
+
+ # Uninstall the whole FSM
+ self.state_machine.teardown()
+
+ #FIXME There should be some amount of resetting done here...
+ self.activity = None
+
+
+ def set_state(self, name):
+ """
+ Switch to a new state
+ """
+ logger.debug("====NEW STATE: %s====" % name)
+
+ self.state_machine.set_state(name)
+
+
+ # Currently unused -- equivalent function is in each state
+ def _eventfilter_state_done(self, eventfilter):
+ """
+ Callback handler for eventfilter to notify
+ when we must go to the next state.
+ """
+ #XXX Tests should be run here normally
+
+ #Swith to the next state pointed by the eventfilter
+ self.set_state(eventfilter.get_next_state())
+
+class State:
+ """
+ This is a step in a tutorial. The state represents a collection of actions
+ to undertake when entering the state, and a series of event filters
+ with associated actions that point to a possible next state.
+ """
+
+ def __init__(self, name, action_list=[], event_filter_list=[], tutorial=None):
+ """
+ Initializes the content of the state, like loading the actions
+ that are required and building the correct tests.
+
+ @param action_list The list of actions to execute when entering this
+ state
+ @param event_filter_list A list of tuples of the form
+ (event_filter, next_state_name), that explains the outgoing links for
+ this state
+ @param tutorial The higher level container of the state
+ """
+ self._actions = action_list
+
+ # Unused for now
+ #self.tests = []
+
+ self._event_filters = event_filter_list
+
+ self.tutorial = tutorial
+
+ def set_tutorial(self, tutorial):
+ """
+ Associates this state with a tutorial. A tutorial must be set prior
+ to executing anything in the state. The reason for this is that the
+ states need to have access to the activity (via the tutorial) in order
+ to properly register their callbacks on the activities' widgets.
+
+ @param tutorial The tutorial that this state runs under.
+ """
+ if self.tutorial == None :
+ self.tutorial = tutorial
+ else:
+ raise RuntimeWarning(\
+ "The state %s was already associated with a tutorial." % self.name)
+
+ def setup(self):
+ """
+ Install the state itself, by first registering the event filters
+ and then triggering the actions.
+ """
+ for eventfilter in self._event_filters:
+ eventfilter.install_handlers(self._event_filter_state_done_cb,
+ activity=self.tutorial.activity)
+
+ for action in self._actions:
+ action.do()
+
+ def teardown(self):
+ """
+ Uninstall all the event filters that were active in this state.
+ Also undo every action that was installed for this state. This means
+ removing dialogs that were displayed, removing highlights, etc...
+ """
+ # Remove the handlers for the all of the state's event filters
+ for event_filter in self._event_filters:
+ event_filter.remove_handlers()
+
+ # Undo all the actions related to this state
+ for action in self._actions:
+ action.undo()
+
+ def _event_filter_state_done_cb(self, event_filter):
+ """
+ Callback for event filters. This function needs to inform the
+ tutorial that the state is over and tell it what is the next state.
+
+ @param event_filter The event filter that was called
+ """
+ # Run the tests here, if need be
+
+ # Warn the higher level that we wish to change state
+ self.tutorial.set_state(event_filter.get_next_state())
+
+ # Unused for now
+## def verify(self):
+## """Run the internal tests to see if one of them passes. If it does,
+## then do the associated processing to go in the next state."""
+## for test in self.tests:
+## if test.verify() == True:
+## actions = test.get_actions()
+## for act in actions:
+## act.do()
+## # Now that we execute the actions related to a test, we might
+## # want to undo them right after --- should we use a callback or
+## # a timer?
+
+class FiniteStateMachine(State):
+ """
+ This is a collection of states, with a start state and an end callback.
+ It is used to simplify the development of the various tutorials by
+ encapsulating a collection of states that represent a given learning
+ process.
+
+ For now, we will consider that there can only be states
+ inserted in the FSM, and that there are no nested FSM inside.
+ """
+
+ def __init__(self, name, tutorial=None, state_dict={}, start_state_name="INIT", action_list=[]):
+ """
+ The constructor for a FSM. Pass in the start state and the setup
+ actions that need to be taken when the FSM itself start (which may be
+ different from what is done in the first state of the machine).
+
+ @param name A short descriptive name for this FSM
+ @param tutorial The tutorial that will execute this FSM. If None is
+ attached on creation, then one must absolutely be attached before
+ executing the FSM with set_tutorial().
+ @param state_dict A dictionary containing the state names as keys and
+ the state themselves as entries.
+ @param start_state_name The name of the starting state, if different
+ from "INIT"
+ @param action_list The actions to undertake when initializing the FSM
+ """
+ State.__init__(self, name)
+
+ self.name = name
+ self.tutorial = tutorial
+
+ # Dictionnary of states contained in the FSM
+ self._states = state_dict
+
+ # Remember the initial state - we might want to reset
+ # or rewind the FSM at a later moment
+ self.start_state = state_dict[start_state_name]
+ self.current_state = self.start_state
+
+ # Register the actions for the FSM - They will be processed at the
+ # FSM level, meaning that when the FSM will start, it will first
+ # execute those actions. When the FSM closes, it will tear down the
+ # inner actions of the state, then close its own actions
+ self.actions = action_list
+
+ # Flag to mention that the FSM was initialized
+ self._fsm_setup_done = False
+ # Flag that must be raised when the FSM is to be teared down
+ self._fsm_teardown_done = False
+ # Flag used to declare that the FSM has reached an end state
+ self._fsm_has_finished = False
+
+ def set_tutorial(self, tutorial):
+ """
+ This associates the FSM to the given tutorial. It MUST be associated
+ either in the constructor or with this function prior to executing the
+ FSM.
+
+ @param tutorial The tutorial that will execute this FSM.
+ """
+ # If there was no tutorial associated
+ if self.tutorial == None:
+ # Associate it with this FSM and all the underlying states
+ self.tutorial = tutorial
+ for state in self._states.itervalues():
+ state.set_tutorial(tutorial)
+ else:
+ raise RuntimeWarning(\
+ "The FSM %s is already associated with a tutorial."%self.name\
+ )
+
+ def setup(self):
+ """
+ This function initializes the FSM the first time it is called.
+ Then, every time it is called, it initializes the current state.
+ """
+ # Are we associated with a tutorial?
+ if self.tutorial == None:
+ raise UnboundLocalError("No tutorial was associated with FSM %s" % self.name)
+
+ # If we never initialized the FSM itself, then we need to run all the
+ # actions associated with the FSM.
+ if self._fsm_setup_done == False:
+ # Flag the FSM level setup as done
+ self._fsm_setup_done = True
+ # Execute all the FSM level actions
+ for action in self.actions:
+ action.do()
+
+ # Then, we need to run the setup of the current state
+ self.current_state.setup()
+
+ def set_state(self, new_state_name):
+ """
+ This functions changes the current state of the finite state machine.
+
+ @param new_state The identifier of the state we need to go to
+ """
+ # TODO : Since we assume no nested FSMs, we don't set state on the
+ # inner States / FSMs
+## # Pass in the name to the internal state - it might be a FSM and
+## # this name will apply to it
+## self.current_state.set_state(new_state_name)
+
+ # Make sure the given state is owned by the FSM
+ if not self._states.has_key(new_state_name):
+ # If we did not recognize the name, then we do not possess any
+ # state by that name - we must ignore this state change request as
+ # it will be done elsewhere in the hierarchy (or it's just bogus).
+ return
+
+ new_state = self._states[new_state_name]
+
+ # Undo the actions of the old state
+ self.teardown()
+
+ # Insert the new state
+ self.current_state = new_state
+
+ # Call the initial actions in the new state
+ self.setup()
+
+
+ def teardown(self):
+ """
+ Revert any changes done by setup()
+ """
+ # Teardown the current state
+ self.current_state.teardown()
+
+ # If we just finished the whole FSM, we need to also call the teardown
+ # on the FSM level actions
+ if self._fsm_has_finished == True:
+ # Flag the FSM teardown as not needed anymore
+ self._fsm_teardown_done = True
+ # Undo all the FSM level actions here
+ for action in self.actions:
+ action.undo()
+
+ #Unused for now
+## def verify(self):
+## """Verify if the current state passes its tests"""
+## return self.current_state.verify()
diff --git a/sugar-toolkit/src/sugar/tutorius/dialog.py b/sugar-toolkit/src/sugar/tutorius/dialog.py
new file mode 100644
index 0000000..be51a0e
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/dialog.py
@@ -0,0 +1,59 @@
+# Copyright (C) 2009, Tutorius.org
+#
+# 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
+"""
+The Dialog module provides means of interacting with the user
+through the use of Dialogs.
+"""
+import gtk
+
+class TutoriusDialog(gtk.Dialog):
+ """
+ TutoriusDialog is a simple wrapper around gtk.Dialog.
+
+ It allows creating and showing a dialog and connecting the response and
+ button click events to callbacks.
+ """
+ def __init__(self, label="Hint", button_clicked_cb=None, response_cb=None):
+ """
+ Constructor.
+
+ @param label text to be shown on the dialog
+ @param button_clicked_cb callback for the button click
+ @param response_cb callback for the dialog response
+ """
+ gtk.Dialog.__init__(self)
+
+ self._button = gtk.Button(label)
+
+ self.add_action_widget(self._button, 1)
+
+ if not button_clicked_cb == None:
+ self._button.connect("clicked", button_clicked_cb)
+
+ self._button.show()
+
+ if not response_cb == None:
+ self.connect("response", response_cb)
+
+ self.set_decorated(False)
+
+ def set_button_clicked_cb(self, funct):
+ """Setter for the button_clicked callback"""
+ self._button.connect("clicked", funct)
+
+ def close_self(self, arg=None):
+ """Close the dialog"""
+ self.destroy()
diff --git a/sugar-toolkit/src/sugar/tutorius/dialog.pyc b/sugar-toolkit/src/sugar/tutorius/dialog.pyc
new file mode 100644
index 0000000..e7e02b9
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/dialog.pyc
Binary files differ
diff --git a/sugar-toolkit/src/sugar/tutorius/dragbox.py b/sugar-toolkit/src/sugar/tutorius/dragbox.py
new file mode 100644
index 0000000..d84e4ee
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/dragbox.py
@@ -0,0 +1,120 @@
+# Copyright (C) 2009, Tutorius.org
+#
+# 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
+"""
+This module defines the DragBox class, representing a gtk.Layout like container
+which supports a "children dragging mode".
+"""
+
+import gtk
+
+class DragBox(gtk.EventBox):
+ """
+ A DragBox represents a gtk container whose child object can be arbitrarily
+ placed, like for a gtk.Layout. However DragBox implements a child "dragging"
+ mode which allows the user to freely move immediate children of the DragBox.
+
+ This can be toggled by the dragMode property (default to False)
+
+ Default behavior is to keep children at minimal size, as returned by
+ a call to gtk.Widget.get_size().
+
+ """
+ def __init__(self):
+ gtk.EventBox.__init__(self)
+
+ self.set_above_child(False)
+ self.connect("motion-notify-event", self._on_mouse_evt)
+ self.connect("button-press-event", self._on_mouse_evt)
+ self.connect("button-release-event", self._on_mouse_evt)
+
+ self._drag_mode = False
+ self._dragging = None # the object being dragged, or None
+ self._last_mouse_pos = None # last known mouse position (win. relative)
+
+ self._layout = gtk.Layout()
+ self.add(self._layout)
+
+ def attach(self, widget, xPos=0, yPos=0, size=None, resizable=False):
+ """
+ Adds the child to the EventBox hierarchy.
+ widget is expected to be a displayable gtk.Widget and will respond
+ to the dragMode by being dragable.
+ """
+ # TODO handle child and set size
+ self._layout.put(child_widget=widget, x=xPos, y=yPos)
+
+ def show(self):
+ gtk.EventBox.show(self)
+ self._layout.show()
+
+ def hide(self):
+ gtk.EventBox.hide(self)
+ self._layout.hide()
+
+ def _on_mouse_evt(self, widget, event, data=None):
+ """
+ Callback registered on mouse events.
+ _on_mouse_evt does the actual child selection and moving
+ """
+ if not self._drag_mode:
+ return
+
+ x, y = event.get_coords()
+
+ if self._dragging is not None and event.type is gtk.gdk.MOTION_NOTIFY:
+ child = self._dragging.get_allocation()
+ self._layout.move(self._dragging,
+ int(child.x+x-self._last_mouse_pos[0]),
+ int(child.y+y-self._last_mouse_pos[1]))
+ self._last_mouse_pos = event.get_coords()
+ # FIXME scale layout or constraint dragging to DragBox
+
+ elif self._dragging is None and event.type is gtk.gdk.BUTTON_PRESS:
+ # iterate in revese as last drawn child is over the others
+ for child in reversed(self._layout.get_children()):
+ rect = child.get_allocation()
+ if x >= rect.x and x < rect.x+rect.width \
+ and y >= rect.y and y < rect.y+rect.height:
+ self._dragging = child
+ # Common interface paradigm is that selecting an item
+ # goes to top of stack it so we do just that.
+ self._layout.remove(child)
+ self._layout.put(child, rect.x, rect.y)
+ break
+ self._last_mouse_pos = event.get_coords()
+ # TODO add conditional restack child
+
+ elif self._dragging is not None \
+ and event.type is gtk.gdk.BUTTON_RELEASE:
+ self._dragging = None
+
+ def _set_drag_mode(self, value):
+ if (not self._drag_mode) and value:
+ self.set_above_child(True)
+ elif self._drag_mode and (not value):
+ self.set_above_child(False)
+
+ self._drag_mode = value
+
+ def _get_drag_mode(self):
+ return self._drag_mode
+
+ dragMode = property(fset=_set_drag_mode, fget=_get_drag_mode,
+ doc="Defines whether widgets in DragBox can be mouse dragged.")
+
+ # TODO set child properties (list_child_properties)
+
+
diff --git a/sugar-toolkit/src/sugar/tutorius/dragbox.pyc b/sugar-toolkit/src/sugar/tutorius/dragbox.pyc
new file mode 100644
index 0000000..568f3e3
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/dragbox.pyc
Binary files differ
diff --git a/sugar-toolkit/src/sugar/tutorius/filters.py b/sugar-toolkit/src/sugar/tutorius/filters.py
new file mode 100644
index 0000000..4c04cf6
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/filters.py
@@ -0,0 +1,162 @@
+# Copyright (C) 2009, Tutorius.org
+# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com>
+#
+# 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 gobject
+
+
+from sugar.tutorius.gtkutils import find_widget
+class EventFilter(object):
+ """
+ Base class for an event filter
+ """
+ def __init__(self, next_state):
+ """
+ Constructor.
+ @param next_state name of the next state
+ """
+ self._next_state = next_state
+ self._callback = None
+
+ def get_next_state(self):
+ """
+ Getter for the next state
+ """
+ return self._next_state
+
+ def install_handlers(self, callback, **kwargs):
+ """
+ install_handlers is called for eventfilters to setup all
+ necessary event handlers to be able to catch the desired
+ event.
+
+ @param callback the callback function that will be called
+ with the event filter as an argument when the event is catched
+ and validated.
+ @param **kwargs unused by this handler for now, allows subclasses
+ to receive information about the context when installing
+
+ Subclasses must call this super method to setup the callback if they
+ feel like cooperating
+ """
+ self._callback = callback
+
+ def remove_handlers(self):
+ """
+ remove_handlers is called when a state is done so that all
+ event filters can cleanup any handlers they have installed
+
+ This function will also clear the callback function so that any
+ leftover handler that is triggered will not be able to change the
+ application state.
+
+ subclasses must call this super method to cleanup the callback if they
+ collaborate and use this classe's do_callback()
+ """
+ self._callback = None
+
+ def do_callback(self, *args, **kwargs):
+ """
+ Default callback function that calls the event filter callback
+ with the event filter as only argument.
+ """
+ if self._callback:
+ self._callback(self)
+
+class TimerEvent(EventFilter):
+ """
+ TimerEvent is a special EventFilter that uses gobject
+ timeouts to trigger a state change after a specified amount
+ of time. It must be used inside a gobject main loop to work.
+ """
+ def __init__(self,next_state,timeout_s):
+ """Constructor.
+
+ @param next_state default EventFilter param, passed on to EventFilter
+ @param timeout_s timeout in seconds
+ """
+ super(TimerEvent,self).__init__(next_state)
+ self._timeout = timeout_s
+ self._handler_id = None
+
+ def install_handlers(self, callback, **kwargs):
+ """install_handlers creates the timer and starts it"""
+ super(TimerEvent,self).install_handlers(callback, **kwargs)
+ #Create the timer
+ self._handler_id = gobject.timeout_add_seconds(self._timeout, self._timeout_cb)
+
+ def remove_handlers(self):
+ """remove handler removes the timer"""
+ super(TimerEvent,self).remove_handlers()
+ if self._handler_id:
+ try:
+ #XXX What happens if this was already triggered?
+ #remove the timer
+ gobject.source_remove(self._handler_id)
+ except:
+ pass
+
+ def _timeout_cb(self):
+ """
+ _timeout_cb triggers the eventfilter callback.
+
+ It is necessary because gobject timers only stop if the callback they
+ trigger returns False
+ """
+ self.do_callback()
+ return False #Stops timeout
+
+class GtkWidgetEventFilter(EventFilter):
+ """
+ Basic Event filter for Gtk widget events
+ """
+ def __init__(self, next_state, object_id, event_name):
+ """Constructor
+ @param next_state default EventFilter param, passed on to EventFilter
+ @param object_id object fqdn-style identifier
+ @param event_name event to attach to
+ """
+ super(GtkWidgetEventFilter,self).__init__(next_state)
+ self._callback = None
+ self._object_id = object_id
+ self._event_name = event_name
+ self._widget = None
+ self._handler_id = None
+
+ def install_handlers(self, callback, **kwargs):
+ """install handlers
+ @param callback default EventFilter callback arg
+ @param activity keyword argument activity must be present to install
+ the event handler into the activity's widget hierarchy
+ """
+ super(GtkWidgetEventFilter, self).install_handlers(callback, **kwargs)
+ if not "activity" in kwargs:
+ raise TypeError("activity argument is Mandatory")
+
+ #find the widget and connect to its event
+ self._widget = find_widget(kwargs["activity"], self._object_id)
+ self._handler_id = self._widget.connect( \
+ self._event_name, self.do_callback )
+
+ def remove_handlers(self):
+ """remove handlers"""
+ super(GtkWidgetEventFilter, self).remove_handlers()
+ #if an event was connected, disconnect it
+ if self._handler_id:
+ self._widget.handler_disconnect(self._handler_id)
+ self._handler_id=None
+
+
diff --git a/sugar-toolkit/src/sugar/tutorius/gtkutils.py b/sugar-toolkit/src/sugar/tutorius/gtkutils.py
new file mode 100644
index 0000000..073a7f3
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/gtkutils.py
@@ -0,0 +1,169 @@
+# Copyright (C) 2009, Tutorius.org
+# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com>
+#
+# 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
+"""
+Utility classes and functions that are gtk related
+"""
+
+def find_widget(base, target_fqdn):
+ """Find a widget by digging into a parent widget's children tree
+ @param base the parent widget
+ @param target_fqdn fqdn-style target object name
+
+ @return widget found
+
+ The object should normally be the activity widget, as it is the root
+ widget for activities. The target_fqdn is a dot separated list of
+ indexes used in widget.get_children and should start with a 0 which is
+ the base widget itself,
+
+ Example Usage:
+ find_widget(activity,"0.0.0.1.0.0.2")
+ """
+ path = target_fqdn.split(".")
+ #We select the first object and pop the first zero
+ obj = base
+ path.pop(0)
+
+ while len(path) > 0:
+ try:
+ obj = obj.get_children()[int(path.pop(0))]
+ except:
+ break
+
+ return obj
+
+EVENTS = [
+ "focus",
+ "button-press-event",
+ "enter-notify-event",
+ "leave-notify-event",
+ "key-press-event",
+ "text-selected",
+ "clicked",
+]
+
+IGNORED_WIDGETS = [
+ "GtkVBox",
+ "GtkHBox",
+ "GtkAlignment",
+ "GtkNotebook",
+ "GtkButton",
+ "GtkToolItem",
+ "GtkToolbar",
+]
+
+def register_signals_numbered(target, handler, prefix="0", max_depth=None):
+ """
+ Recursive function to register event handlers on an target
+ and it's children. The event handler is called with an extra
+ argument which is a two-tuple containing the signal name and
+ the FQDN-style name of the target that triggered the event.
+
+ This function registers all of the events listed in
+ EVENTS
+
+ Example arg tuple added:
+ ("focus", "1.1.2")
+ Side effects:
+ -Handlers connected on the various targets
+
+ @param target the target to recurse on
+ @param handler the handler function to connect
+ @param prefix name prepended to the target name to form a chain
+ @param max_depth maximum recursion depth, None for infinity
+
+ @returns list of (object, handler_id)
+ """
+ ret = []
+ #Gtk Containers have a get_children() function
+ if hasattr(target, "get_children") and \
+ hasattr(target.get_children, "__call__"):
+ children = target.get_children()
+ for i in range(len(children)):
+ child = children[i]
+ if max_depth is None or max_depth > 0:
+ #Recurse with a prefix on all children
+ pre = ".".join( \
+ [p for p in (prefix, str(i)) if not p is None]
+ )
+ if max_depth is None:
+ dep = None
+ else:
+ dep = max_depth - 1
+ ret+=register_signals_numbered(child, handler, pre, dep)
+ #register events on the target if a widget XXX necessary to check this?
+ if isinstance(target, gtk.Widget):
+ for sig in Tutorial.EVENTS:
+ try:
+ ret.append( \
+ (target, target.connect(sig, handler, (sig, prefix) ))\
+ )
+ except TypeError:
+ pass
+
+ return ret
+
+def register_signals(self, target, handler, prefix=None, max_depth=None):
+ """
+ Recursive function to register event handlers on an target
+ and it's children. The event handler is called with an extra
+ argument which is a two-tuple containing the signal name and
+ the FQDN-style name of the target that triggered the event.
+
+ This function registers all of the events listed in
+ Tutorial.EVENTS and omits widgets with a name matching
+ Tutorial.IGNORED_WIDGETS from the name hierarchy.
+
+ Example arg tuple added:
+ ("focus", "Activity.Toolbox.Bold")
+ Side effects:
+ -Handlers connected on the various targets
+ -Handler ID's stored in self.handlers
+
+ @param target the target to recurse on
+ @param handler the handler function to connect
+ @param prefix name prepended to the target name to form a chain
+ @param max_depth maximum recursion depth, None for infinity
+ """
+ ret = []
+ #Gtk Containers have a get_children() function
+ if hasattr(target, "get_children") and \
+ hasattr(target.get_children, "__call__"):
+ for child in target.get_children():
+ if max_depth is None or max_depth > 0:
+ #Recurse with a prefix on all children
+ pre = ".".join( \
+ [p for p in (prefix, target.get_name()) \
+ if not (p is None or p in Tutorial.IGNORED_WIDGETS)] \
+ )
+ ret += register_signals(child, handler, pre, max_depth-1)
+ name = ".".join( \
+ [p for p in (prefix, target.get_name()) \
+ if not (p is None or p in Tutorial.IGNORED_WIDGETS)] \
+ )
+ #register events on the target if a widget XXX necessary to check this?
+ if isinstance(target, gtk.Widget):
+ for sig in Tutorial.EVENTS:
+ try:
+ ret.append( \
+ (target, target.connect(sig, handler, (sig, name) )) \
+ )
+ except TypeError:
+ pass
+
+ return ret
+
diff --git a/sugar-toolkit/src/sugar/tutorius/overlayer.py b/sugar-toolkit/src/sugar/tutorius/overlayer.py
new file mode 100644
index 0000000..c08ed4c
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/overlayer.py
@@ -0,0 +1,328 @@
+"""
+This guy manages drawing of overlayed widgets. The class responsible for drawing
+management (Overlayer) and overlayable widgets are defined here.
+"""
+# Copyright (C) 2009, Tutorius.org
+#
+# 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 gobject
+import gtk
+import cairo
+import pangocairo
+
+# This is the CanvasDrawable protocol. Any widget wishing to be drawn on the
+# overlay must implement it. See TextBubble for a sample implementation.
+#class CanvasDrawable(object):
+# """Defines the CanvasDrawable protocol"""
+# no_expose = None
+# def draw_with_context(self, context):
+# """
+# Draws the cairo widget with the passed cairo context.
+# This will be called if the widget is child of an overlayer.
+# """
+# pass
+
+class Overlayer(gtk.Layout):
+ """
+ This guy manages drawing of overlayed widgets. Those can be standard GTK
+ widgets or special "cairoDrawable" widgets which support the defined
+ interface (see the put method).
+
+ @param overlayed widget to be overlayed. Will be resized to full size.
+ """
+ def __init__(self, overlayed=None):
+ gtk.Layout.__init__(self)
+
+ self._overlayed = overlayed
+ if overlayed:
+ self.put(overlayed, 0, 0)
+
+ self.__realizer = self.connect("expose-event", self.__init_realized)
+ self.connect("size-allocate", self.__size_allocate)
+ self.show()
+
+ self.__render_handle = None
+
+ def put(self, child, x, y):
+ """
+ Adds a child widget to be overlayed. This can be, overlay widgets or
+ normal GTK widgets (though normal widgets will alwas appear under
+ cairo widgets due to the rendering chain).
+
+ @param child the child to add
+ @param x the horizontal coordinate for positionning
+ @param y the vertical coordinate for positionning
+ """
+ if hasattr(child, "draw_with_context"):
+ # if the widget has the CanvasDrawable protocol, use it.
+ child.no_expose = True
+ gtk.Layout.put(self, child, x, y)
+
+
+ def __init_realized(self, widget, event):
+ """
+ Initializer to set once widget is realized.
+ Since an expose event is signaled only to realized widgets, we set this
+ callback for the first expose run. It should also be called after
+ beign reparented to ensure the window used for drawing is set up.
+ """
+ assert hasattr(self.window, "set_composited"), \
+ "compositing not supported or widget not realized."
+ self.disconnect(self.__realizer)
+ del self.__realizer
+
+ self.parent.set_app_paintable(True)
+
+ # the parent is composited, so we can access gtk's rendered buffer
+ # and overlay over. If we don't composite, we won't be able to read
+ # pixels and background will be black.
+ self.window.set_composited(True)
+ self.__render_handle = self.parent.connect_after("expose-event", \
+ self.__expose_overlay)
+
+ def __expose_overlay(self, widget, event):
+ """expose event handler to draw the thing."""
+ #get our child (in this case, the event box)
+ child = widget.get_child()
+
+ #create a cairo context to draw to the window
+ ctx = widget.window.cairo_create()
+
+ #the source data is the (composited) event box
+ ctx.set_source_pixmap(child.window,
+ child.allocation.x,
+ child.allocation.y)
+
+ #draw no more than our expose event intersects our child
+ region = gtk.gdk.region_rectangle(child.allocation)
+ rect = gtk.gdk.region_rectangle(event.area)
+ region.intersect(rect)
+ ctx.region (region)
+ ctx.clip()
+
+ ctx.set_operator(cairo.OPERATOR_OVER)
+ # has to be blended and a 1.0 alpha would not make it blend
+ ctx.paint_with_alpha(0.99)
+
+ #draw overlay
+ for drawn_child in self.get_children():
+ if hasattr(drawn_child, "draw_with_context"):
+ drawn_child.draw_with_context(ctx)
+
+
+ def __size_allocate(self, widget, allocation):
+ """
+ Set size allocation (actual gtk widget size) and propagate it to
+ overlayed child
+ """
+ self.allocation = allocation
+ # One may wonder why using size_request instead of size_allocate;
+ # Since widget is laid out in a Layout box, the Layout will honor the
+ # requested size. Using size_allocate could make a nasty nested loop in
+ # some cases.
+ self._overlayed.set_size_request(allocation.width, allocation.height)
+
+
+class TextBubble(gtk.Widget):
+ """
+ A CanvasDrawableWidget drawing a round textbox and a tail pointing
+ to a specified widget.
+ """
+ def __init__(self, text, speaker=None, tailpos=None):
+ """
+ Creates a new cairo rendered text bubble.
+
+ @param text the text to render in the bubble
+ @param speaker the widget to compute the tail position from
+ @param tailpos (optional) position relative to the bubble to use as
+ the tail position, if no speaker
+ """
+ gtk.Widget.__init__(self)
+
+ # FIXME: ensure previous call does not interfere with widget stacking,
+ # as using a gtk.Layout and stacking widgets may reveal a screwed up
+ # order with the cairo widget on top.
+ self.__label = None
+ self.__text_dimentions = None
+
+ self.label = text
+ self.speaker = speaker
+ self.tailpos = tailpos
+ self.line_width = 5
+
+ self.__exposer = self.connect("expose-event", self.__on_expose)
+
+ def draw_with_context(self, context):
+ """
+ Draw using the passed cairo context instead of creating a new cairo
+ context. This eases blending between multiple cairo-rendered
+ widgets.
+ """
+ context.translate(self.allocation.x, self.allocation.y)
+ width = self.allocation.width
+ height = self.allocation.height
+ xradius = width/2
+ yradius = height/2
+ width -= self.line_width
+ height -= self.line_width
+
+ # bubble border
+ context.move_to(self.line_width, yradius)
+ context.curve_to(self.line_width, self.line_width,
+ self.line_width, self.line_width, xradius, self.line_width)
+ context.curve_to(width, self.line_width,
+ width, self.line_width, width, yradius)
+ context.curve_to(width, height, width, height, xradius, height)
+ context.curve_to(self.line_width, height,
+ self.line_width, height, self.line_width, yradius)
+ context.set_line_width(self.line_width)
+ context.set_source_rgb(0.0, 0.0, 0.0)
+ context.stroke()
+
+ # TODO fetch speaker coordinates
+
+ # draw bubble tail
+ if self.tailpos:
+ context.move_to(xradius-40, yradius)
+ context.line_to(self.tailpos[0], self.tailpos[1])
+ context.line_to(xradius+40, yradius)
+ context.set_line_width(self.line_width)
+ context.set_source_rgb(0.0, 0.0, 0.0)
+ context.stroke_preserve()
+ context.set_source_rgb(1.0, 1.0, 0.0)
+ context.fill()
+
+ # bubble painting. Redrawing the inside after the tail will combine
+ # both shapes.
+ # TODO: we could probably generate the shape at initialization to
+ # lighten computations.
+ context.move_to(self.line_width, yradius)
+ context.curve_to(self.line_width, self.line_width,
+ self.line_width, self.line_width, xradius, self.line_width)
+ context.curve_to(width, self.line_width,
+ width, self.line_width, width, yradius)
+ context.curve_to(width, height, width, height, xradius, height)
+ context.curve_to(self.line_width, height,
+ self.line_width, height, self.line_width, yradius)
+ context.set_source_rgb(1.0, 1.0, 0.0)
+ context.fill()
+
+ # text
+ # FIXME create text layout when setting text or in realize method
+ context.set_source_rgb(0.0, 0.0, 0.0)
+ pangoctx = pangocairo.CairoContext(context)
+ text_layout = pangoctx.create_layout()
+ text_layout.set_text(self.__label)
+ pangoctx.move_to(
+ int((self.allocation.width-self.__text_dimentions[0])/2),
+ int((self.allocation.height-self.__text_dimentions[1])/2))
+ pangoctx.show_layout(text_layout)
+
+ # work done. Be kind to next cairo widgets and reset matrix.
+ context.identity_matrix()
+
+ def do_realize(self):
+ """ Setup gdk window creation. """
+ self.set_flags(gtk.REALIZED | gtk.NO_WINDOW)
+ # TODO: cleanup window creation code as lot here probably isn't
+ # necessary.
+ # See http://www.learningpython.com/2006/07/25/writing-a-custom-widget-using-pygtk/
+ # as the following was taken there.
+ self.window = self.get_parent_window()
+ if not isinstance(self.parent, Overlayer):
+ self.unset_flags(gtk.NO_WINDOW)
+ self.window = gtk.gdk.Window(
+ self.get_parent_window(),
+ width=self.allocation.width,
+ height=self.allocation.height,
+ window_type=gtk.gdk.WINDOW_CHILD,
+ wclass=gtk.gdk.INPUT_OUTPUT,
+ event_mask=self.get_events()|gtk.gdk.EXPOSURE_MASK)
+
+ # Associate the gdk.Window with ourselves, Gtk+ needs a reference
+ # between the widget and the gdk window
+ self.window.set_user_data(self)
+
+ # Attach the style to the gdk.Window, a style contains colors and
+ # GC contextes used for drawing
+ self.style.attach(self.window)
+
+ # The default color of the background should be what
+ # the style (theme engine) tells us.
+ self.style.set_background(self.window, gtk.STATE_NORMAL)
+ self.window.move_resize(*self.allocation)
+
+ def __on_expose(self, widget, event):
+ """Redraw event callback."""
+ ctx = self.window.cairo_create()
+
+ self.draw_with_context(ctx)
+
+ return True
+
+ def _set_label(self, value):
+ """Sets the label and flags the widget to be redrawn."""
+ self.__label = value
+ # FIXME hack to calculate size. necessary because may not have been
+ # realized. We create a fake surface to use builtin math. This should
+ # probably be done at realization and/or on text setter.
+ surf = cairo.SVGSurface("/dev/null", 0, 0)
+ ctx = cairo.Context(surf)
+ pangoctx = pangocairo.CairoContext(ctx)
+ text_layout = pangoctx.create_layout()
+ text_layout.set_text(value)
+ self.__text_dimentions = text_layout.get_pixel_size()
+ del text_layout, pangoctx, ctx, surf
+
+ def do_size_request(self, requisition):
+ """Fill requisition with size occupied by the widget."""
+ width, height = self.__text_dimentions
+
+ # FIXME bogus values follows. will need to replace them with
+ # padding relative to font size and line border size
+ requisition.width = int(width+30)
+ requisition.height = int(height+40)
+
+ def do_size_allocate(self, allocation):
+ """Save zone allocated to the widget."""
+ self.allocation = allocation
+
+ def _get_label(self):
+ """Getter method for the label property"""
+ return self.__label
+
+ def _set_no_expose(self, value):
+ """setter for no_expose property"""
+ if self.__exposer and value:
+ self.disconnect(self.__exposer)
+ self.__exposer = None
+ elif (not self.__exposer) and (not value):
+ self.__exposer = self.connect("expose-event", self.__on_expose)
+
+ def _get_no_expose(self):
+ """getter for no_expose property"""
+ return not self.__exposer
+
+ no_expose = property(fset=_set_no_expose, fget=_get_no_expose,
+ doc="Whether the widget should handle exposition events or not.")
+
+ label = property(fget=_get_label, fset=_set_label,
+ doc="Text label which is to be painted on the top of the widget")
+
+gobject.type_register(TextBubble)
+
+
+# vim:set ts=4 sts=4 sw=4 et:
diff --git a/sugar-toolkit/src/sugar/tutorius/overlayer.pyc b/sugar-toolkit/src/sugar/tutorius/overlayer.pyc
new file mode 100644
index 0000000..627ba9d
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/overlayer.pyc
Binary files differ
diff --git a/sugar-toolkit/src/sugar/tutorius/services.py b/sugar-toolkit/src/sugar/tutorius/services.py
new file mode 100644
index 0000000..467eca0
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/services.py
@@ -0,0 +1,68 @@
+# Copyright (C) 2009, Tutorius.org
+# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com>
+#
+# 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
+"""
+Services
+
+This module supplies services to be used by States, FSMs, Actions and Filters.
+
+Services provided are:
+-Access to the running activity
+-Access to the running tutorial
+"""
+
+
+class ObjectStore(object):
+ #Begin Singleton code
+ instance=None
+ def __new__(cls):
+ if not ObjectStore.instance:
+ ObjectStore.instance = ObjectStore.__ObjectStore()
+
+ return ObjectStore.instance
+
+ #End Singleton code
+ class __ObjectStore(object):
+ """
+ The Object Store is a singleton class that allows access to
+ the current runnign activity and tutorial.
+ """
+ def __init__(self):
+ self._activity = None
+ self._tutorial = None
+ #self._fsm_path = []
+
+ def set_activity(self, activity):
+ """Setter for activity"""
+ self._activity = activity
+
+ def get_activity(self):
+ """Getter for activity"""
+ return self._activity
+
+ activity = property(fset=set_activity,fget=get_activity,doc="activity")
+
+ def set_tutorial(self, tutorial):
+ """Setter for tutorial"""
+ self._tutorial = tutorial
+
+ def get_tutorial(self):
+ """Getter for tutorial"""
+ return self._tutorial
+
+ tutorial = property(fset=set_tutorial,fget=get_tutorial,doc="tutorial")
+
+ __doc__ = __ObjectStore.__doc__
diff --git a/sugar-toolkit/src/sugar/tutorius/tests/.coverage b/sugar-toolkit/src/sugar/tutorius/tests/.coverage
new file mode 100644
index 0000000..eb89cb2
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/tests/.coverage
@@ -0,0 +1 @@
+{0 \ No newline at end of file
diff --git a/sugar-toolkit/src/sugar/tutorius/tests/coretests.py b/sugar-toolkit/src/sugar/tutorius/tests/coretests.py
new file mode 100644
index 0000000..ed5a7c0
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/tests/coretests.py
@@ -0,0 +1,197 @@
+# Copyright (C) 2009, Tutorius.org
+# Copyright (C) 2009, Michael Janelle-Montcalm <michael.jmontcalm@gmail.com>
+#
+# 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
+"""
+Core Tests
+
+This module contains all the tests that pertain to the usage of the Tutorius
+Core. This means that the Event Filters, the Finite State Machine and all the
+related elements and interfaces are tested here.
+
+"""
+
+import unittest
+
+import logging
+from sugar.tutorius.actions import Action, OnceWrapper
+from sugar.tutorius.core import *
+from sugar.tutorius.filters import *
+
+# Helper classes to help testing
+class SimpleTutorial(Tutorial):
+ """
+ Fake tutorial
+ """
+ def __init__(self, start_name="INIT"):
+ #Tutorial.__init__(self, "Simple Tutorial", None)
+ self.current_state_name = start_name
+ self.activity = "TODO : This should be an activity"
+
+ def set_state(self, name):
+ self.current_state_name = name
+
+class TrueWhileActiveAction(Action):
+ """
+ This action's active member is set to True after a do and to False after
+ an undo.
+
+ Used to verify that a State correctly triggers the do and undo actions.
+ """
+ def __init__(self):
+ self.active = False
+
+ def do(self):
+ self.active = True
+
+ def undo(self):
+ self.active = False
+
+
+class CountAction(Action):
+ """
+ This action counts how many times it's do and undo methods get called
+ """
+ def __init__(self):
+ self.do_count = 0
+ self.undo_count = 0
+
+ def do(self):
+ self.do_count += 1
+
+ def undo(self):
+ self.undo_count += 1
+
+class TriggerEventFilter(EventFilter):
+ """
+ This event filter can be triggered by simply calling its execute function.
+
+ Used to fake events and see the effect on the FSM.
+ """
+ def __init__(self, next_state):
+ EventFilter.__init__(self, next_state)
+ self.toggle_on_callback = False
+
+ def install_handlers(self, callback, **kwargs):
+ """
+ Forsakes the incoming callback function and just set the inner one.
+ """
+ self._callback = self._inner_cb
+
+ def _inner_cb(self, event_filter):
+ self.toggle_on_callback = not self.toggle_on_callback
+
+class OnceWrapperTests(unittest.TestCase):
+ def test_onceaction_toggle(self):
+ """
+ Validate that the OnceWrapper wrapper works properly using the
+ CountAction
+ """
+ act = CountAction()
+ wrap = OnceWrapper(act)
+
+ assert act.do_count == 0, "do() should not have been called in __init__()"
+ assert act.undo_count == 0, "undo() should not have been called in __init__()"
+
+ wrap.undo()
+
+ assert act.undo_count == 0, "undo() should not be called if do() has not been called"
+
+ wrap.do()
+ assert act.do_count == 1, "do() should have been called once"
+
+ wrap.do()
+ assert act.do_count == 1, "do() should have been called only once"
+
+ wrap.undo()
+ assert act.undo_count == 1, "undo() should have been called once"
+
+ wrap.undo()
+ assert act.undo_count == 1, "undo() should have been called only once"
+
+
+# State testing class
+class StateTest(unittest.TestCase):
+ """
+ This class has to test the State interface as well as the expected
+ functionality.
+ """
+
+ def test_action_toggle(self):
+ """
+ Validate that the actions are properly done on setup and undone on
+ teardown.
+
+ Pretty awesome.
+ """
+ act = TrueWhileActiveAction()
+
+ state = State("action_test", action_list=[act])
+
+ assert act.active == False, "Action is not initialized properly"
+
+ state.setup()
+
+ assert act.active == True, "Action was not triggered properly"
+
+ state.teardown()
+
+ assert act.active == False, "Action was not undone properly"
+
+ def test_event_filter(self):
+ """
+ Tests the fact that the event filters are correctly installed on setup
+ and uninstalled on teardown.
+ """
+ event_filter = TriggerEventFilter("second_state")
+
+ state = State("event_test", event_filter_list=[event_filter])
+ state.set_tutorial(SimpleTutorial())
+
+ assert event_filter.toggle_on_callback == False, "Wrong init of event_filter"
+ assert event_filter._callback == None, "Event filter has a registered callback before installing handlers"
+
+ state.setup()
+
+ assert event_filter._callback != None, "Event filter did not register callback!"
+
+ # 'Trigger' the event - This is more like a EventFilter test.
+ event_filter.do_callback()
+
+ assert event_filter.toggle_on_callback == True, "Event filter did not execute callback"
+
+ state.teardown()
+
+ assert event_filter._callback == None, "Event filter did not remove callback properly"
+
+ def test_warning_set_tutorial_twice(self):
+ """
+ Calls set_tutorial twice and expects a warning on the second.
+ """
+ state = State("start_state")
+ tut = SimpleTutorial("First")
+ tut2 = SimpleTutorial("Second")
+
+ state.set_tutorial(tut)
+
+ try:
+ state.set_tutorial(tut2)
+ assert False, "No RuntimeWarning was raised on second set_tutorial"
+ except :
+ pass
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/sugar-toolkit/src/sugar/tutorius/tests/coretests.pyc b/sugar-toolkit/src/sugar/tutorius/tests/coretests.pyc
new file mode 100644
index 0000000..5adf79e
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/tests/coretests.pyc
Binary files differ
diff --git a/sugar-toolkit/src/sugar/tutorius/tests/overlaytests.py b/sugar-toolkit/src/sugar/tutorius/tests/overlaytests.py
new file mode 100644
index 0000000..b5fd209
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/tests/overlaytests.py
@@ -0,0 +1,115 @@
+# Copyright (C) 2009, Tutorius.org
+# Copyright (C) 2009, Simon Poirier <simpoir@gmail.com>
+#
+# 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
+"""
+GUI Tests
+
+This module contains all the tests that pertain to the usage of the Tutorius
+overlay mechanism used to display objects on top of the application.
+"""
+
+import unittest
+
+import logging
+import gtk, gobject
+from sugar.tutorius.actions import Action, BubbleMessage
+import sugar.tutorius.overlayer as overlayer
+
+class CanvasDrawable(object):
+ def __init__(self):
+ self._no_expose = False
+ self.exposition_count = 0
+ def _set_no_expose(self, value):
+ self._no_expose = value
+ def draw_with_context(self, context):
+ self.exposition_count += 1
+ no_expose = property(fset=_set_no_expose)
+
+
+class OverlayerTest(unittest.TestCase):
+ def test_cairodrawable_iface(self):
+ """
+ Quickly validates that all our cairo widgets have a minimal interface
+ implemented.
+ """
+ drawables = [overlayer.TextBubble]
+ for widget in drawables:
+ for attr in filter(lambda s:s[0]!='_', dir(CanvasDrawable)):
+ assert hasattr(widget, attr), \
+ "%s not implementing CanvasDrawable iface"%widget.__name__
+
+
+ def test_drawn(self):
+ """
+ Ensures a cairo widget draw method is called at least once in
+ a real gui app.
+ """
+ win = gtk.Window(type=gtk.WINDOW_TOPLEVEL)
+
+ btn = gtk.Button()
+ btn.show()
+ overlay = overlayer.Overlayer(btn)
+ win.add(overlay)
+ # let's also try to draw substitute button label
+ lbl = overlayer.TextBubble("test!")
+ assert lbl.label == 'test!', \
+ "label property mismatch"
+ btn.show()
+ lbl.show()
+ btn.add(lbl)
+
+ lbl.no_expose = True
+ assert lbl.no_expose, "wrong no_expose evaluation"
+ lbl.no_expose = False
+ assert not lbl.no_expose, "wrong no_expose evaluation"
+
+
+ widget = overlayer.TextBubble("testing msg!", tailpos=(10,-20))
+ widget.exposition_count = 0
+ # override draw method
+ def counter(ctx, self=widget):
+ self.exposition_count += 1
+ self.real_exposer(ctx)
+ widget.real_exposer = widget.draw_with_context
+ widget.draw_with_context = counter
+ # centering allows to test the blending with the label
+ overlay.put(widget, 50, 50)
+ widget.show()
+ assert widget.no_expose, \
+ "Overlay should overide exposition handling of widget"
+ assert not lbl.no_expose, \
+ "Non-overlayed cairo should expose as usual"
+
+ # force widget realization
+ # the child is flagged to be redrawn, the overlay should redraw too.
+ win.set_default_size(100, 100)
+ win.show()
+
+ while gtk.events_pending():
+ gtk.main_iteration(block=False)
+ # visual validation: there should be 2 visible bubbles, one as label,
+ # one as overlay
+ import time
+ time.sleep(1)
+ # as x11 events are asynchronous, wait a bit before assuming it went
+ # wrong.
+ while gtk.events_pending():
+ gtk.main_iteration(block=False)
+ assert widget.exposition_count>0, "overlay widget should expose"
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/sugar-toolkit/src/sugar/tutorius/tests/overlaytests.pyc b/sugar-toolkit/src/sugar/tutorius/tests/overlaytests.pyc
new file mode 100644
index 0000000..8d7d533
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/tests/overlaytests.pyc
Binary files differ
diff --git a/sugar-toolkit/src/sugar/tutorius/tests/run-tests.py b/sugar-toolkit/src/sugar/tutorius/tests/run-tests.py
new file mode 100755
index 0000000..74efd64
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/tests/run-tests.py
@@ -0,0 +1,12 @@
+#!/usr/bin/python
+# This is a dumb script to run tests on the sugar-jhbuild installed files
+# The path added is the default path for the jhbuild build
+
+import os, sys
+sys.path.insert(0,
+ os.path.abspath("../../../../../../install/lib/python2.5/site-packages/")
+)
+import unittest
+from coretests import *
+
+unittest.main()
diff --git a/sugar-toolkit/src/sugar/tutorius/testwin.py b/sugar-toolkit/src/sugar/tutorius/testwin.py
new file mode 100644
index 0000000..ef92b7f
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/testwin.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+# Copyright (C) 2009, Tutorius.org
+#
+# 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 dragbox
+import textbubble
+
+box = None
+
+def _destroy(widget, data=None):
+ gtk.main_quit()
+
+def _delete_event(widget, event, data=None):
+ print "quitting"
+ return False
+
+def blublu(widget, data=""):
+ print data
+
+def _drag_toggle(widget, data=None):
+ global box
+ box.dragMode = not box.dragMode
+
+
+def addBtn(widget, data, bubble=0, btns=[0]):
+ if bubble == 1:
+ bt = textbubble.TextBubble("Bubble(%d)"%btns[0])
+ else:
+ bt = gtk.Button("Bubble(%d)"%btns[0])
+ ##bt.set_size_request(60,40)
+ bt.show()
+ data.attach(bt)
+ btns[0] += 1
+
+def main():
+ global box
+ win = gtk.Window(type=gtk.WINDOW_TOPLEVEL)
+ win.connect("delete_event", _delete_event)
+ win.connect("destroy", _destroy)
+
+ win.set_default_size(800,600)
+
+ vbox = gtk.VBox()
+ vbox.show()
+ win.add(vbox)
+
+ check = gtk.CheckButton(label="dragMode")
+ check.connect("toggled", _drag_toggle)
+ check.show()
+ vbox.pack_start(check, expand=False)
+
+ btnadd = gtk.Button("Add Bubble")
+ btnadd.show()
+ vbox.pack_start(btnadd, expand=False)
+ btnadd2 = gtk.Button("Add Button")
+ btnadd2.show()
+ vbox.pack_start(btnadd2, expand=False)
+
+## bubble = textbubble.TextBubble("Bubbles!")
+## bubble.show()
+## bubble.set_size_request(40,40)
+## vbox.pack_start(bubble, expand=False)
+
+ box = dragbox.DragBox()
+ box.set_border_width(10)
+ box.show()
+ vbox.pack_start(box, expand=True, fill=True)
+
+ btnadd.connect("clicked", addBtn, box, 1)
+ btnadd2.connect("clicked", addBtn, box)
+
+ win.show()
+ gtk.main()
+
+
+if __name__ == "__main__":
+ main()
+
diff --git a/sugar-toolkit/src/sugar/tutorius/textbubble.py b/sugar-toolkit/src/sugar/tutorius/textbubble.py
new file mode 100644
index 0000000..e09b298
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/textbubble.py
@@ -0,0 +1,109 @@
+# Copyright (C) 2009, Tutorius.org
+#
+# 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
+"""
+This module represents TextBubble widget. Also, it aims to be a short example
+of drawing with Cairo.
+"""
+
+import gtk
+from math import pi as M_PI
+import cairo
+
+# FIXME set as subclass of gtk.Widget, not EventBox
+class TextBubble(gtk.EventBox):
+ def __init__(self, label):
+ gtk.EventBox.__init__(self)
+
+ ##self.set_app_paintable(True) # else may be blank
+ # FIXME ensure previous call does not interfere with widget stacking
+ self.label = label
+ self.lineWidth = 5
+
+ self.connect("expose-event", self._on_expose)
+
+ def __draw_with_cairo__(self, context):
+ """
+
+ """
+ pass
+
+ def _on_expose(self, widget, event):
+ """Redraw event callback."""
+ # TODO
+ ctx = self.window.cairo_create()
+
+ # set drawing region. Useless since this widget has its own window.
+ ##region = gtk.gdk.region_rectangle(self.allocation)
+ ##region.intersect(gtk.gdk.region_rectangle(event.area))
+ ##ctx.region(region)
+ ##ctx.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
+ ##ctx.clip()
+
+ ##import pdb; pdb.set_trace()
+ ##ctx.set_operator(cairo.OPERATOR_CLEAR)
+ ##ctx.paint()
+ ##ctx.set_operator(cairo.OPERATOR_OVER)
+
+ width = self.allocation.width
+ height = self.allocation.height
+ xradius = width/2
+ yradius = height/2
+ width -= self.lineWidth
+ height -= self.lineWidth
+ ctx.move_to(self.lineWidth, yradius)
+ ctx.curve_to(self.lineWidth, self.lineWidth,
+ self.lineWidth, self.lineWidth, xradius, self.lineWidth)
+ ctx.curve_to(width, self.lineWidth,
+ width, self.lineWidth, width, yradius)
+ ctx.curve_to(width, height, width, height, xradius, height)
+ ctx.curve_to(self.lineWidth, height,
+ self.lineWidth, height, self.lineWidth, yradius)
+ ctx.set_source_rgb(1.0, 1.0, 1.0)
+ ctx.fill_preserve()
+ ctx.set_line_width(self.lineWidth)
+ ctx.set_source_rgb(0.0, 0.0, 0.0)
+ ctx.stroke()
+
+ _, _, textWidth, textHeight, _, _ = ctx.text_extents(self._label)
+ ctx.move_to(int((self.allocation.width-textWidth)/2),
+ int((self.allocation.height+textHeight)/2))
+ ctx.text_path(self._label)
+ ctx.fill()
+
+ return True
+
+
+ def _set_label(self, value):
+ """Sets the label and flags the widget to be redrawn."""
+ self._label = value
+ # FIXME hack to calculate size. necessary because may not have been
+ # realized
+ surf = cairo.SVGSurface("/dev/null", 0, 0)
+ ctx = cairo.Context(surf)
+ _, _, width, height, _, _ = ctx.text_extents(self._label)
+ del ctx, surf
+
+ # FIXME bogus values follows
+ self.set_size_request(int(width+20), int(height+40))
+ # TODO test changing a realized label
+
+ def _get_label(self):
+ """Getter method for the label property"""
+ return self._label
+
+ label = property(fget=_get_label, fset=_set_label,\
+ doc="Text label which is to be painted on the top of the widget")
+
diff --git a/sugar-toolkit/src/sugar/tutorius/textbubble.pyc b/sugar-toolkit/src/sugar/tutorius/textbubble.pyc
new file mode 100644
index 0000000..c16cb0f
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/textbubble.pyc
Binary files differ
diff --git a/sugar-toolkit/src/sugar/tutorius/tutorial.py b/sugar-toolkit/src/sugar/tutorius/tutorial.py
new file mode 100644
index 0000000..5236127
--- /dev/null
+++ b/sugar-toolkit/src/sugar/tutorius/tutorial.py
@@ -0,0 +1,162 @@
+# Copyright (C) 2009, Tutorius.org
+# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com>
+#
+# 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 logging
+
+from sugar.tutorius.dialog import TutoriusDialog
+
+
+logger = logging.getLogger("tutorius")
+
+class Event:
+ def __init__(self, object_name, event_name ):
+ self.object_name = object_name
+ self.event_name = event_name
+
+ def test(self, sig, name):
+ if self.object_name == name and self.event_name == sig:
+ return True
+ return False
+
+
+class Tutorial (object):
+ EVENTS = [
+ "focus",
+ "button-press-event",
+ "enter-notify-event",
+ "leave-notify-event",
+ "key-press-event",
+ "text-selected",
+ "clicked",
+ ]
+
+ IGNORED_WIDGETS = [
+ "GtkVBox",
+ "GtkHBox",
+ "GtkAlignment",
+ "GtkNotebook",
+ "GtkButton",
+ "GtkToolItem",
+ "GtkToolbar",
+ ]
+
+ def __init__(self, name, fsm):
+ object.__init__(self)
+ self.name = name
+ self.state_machine = fsm
+
+ self.handlers = []
+ self.activity = None
+ #self.setState("INIT")
+ #self.state="INIT"
+ #self.register_signals(self.activity, self.handleEvent, max_depth=10)
+
+ def attach(self, activity):
+ #For now, absolutely detach if a previous one!
+ if self.activity:
+ self.detach()
+ self.activity = activity
+ self.state="INIT"
+ self.register_signals(self.activity,self.handleEvent, max_depth=10)
+
+ def detach(self):
+ self.disconnectHandlers()
+ self.activity = None
+
+ def handleEvent(self, *args):
+ sig, objname = args[-1]
+ logger.debug("EVENT %s ON %s" % (sig, objname) )
+ for transition, next in self.state_machine[self.state]["Events"]:
+ if transition.test(sig,objname):
+ logger.debug("====NEW STATE: %s====" % next)
+ self.state = next
+ dlg = TutoriusDialog(self.state_machine[self.state]["Message"])
+ dlg.setButtonClickedCallback(dlg.closeSelf)
+ dlg.run()
+
+# @staticmethod
+# def logEvent(obj, *args):
+# logger.debug("%s" % str(args[-1]))
+
+ def disconnectHandlers(self):
+ for t, id in self.handlers:
+ t.disconnect_handler(id)
+
+# def setState(self,name):
+# self.disconnectHandlers()
+# self.state = name
+# newstate = ABIWORD_MEF.get(name,())
+# for event, n in newstate:
+# target = self.activity
+# try:
+# for obj in event.object_name.split("."):
+# target = getattr(target,obj)
+# id = target.connect(self.handler,(event.object_name, event.event_name))
+# self.handlers.append(target, id)
+# id = target.connect(Tutorial.logEvent,"EVENT %s ON %s" % (event.object_name, event.event_name))
+# self.handlers.append(target, id)
+# except Exception, e:
+# logger.debug(str(e))
+
+ def register_signals(self,object,handler,prefix=None,max_depth=None):
+ """
+ Recursive function to register event handlers on an object
+ and it's children. The event handler is called with an extra
+ argument which is a two-tuple containing the signal name and
+ the FQDN-style name of the object that triggered the event.
+
+ This function registers all of the events listed in
+ Tutorial.EVENTS and omits widgets with a name matching
+ Tutorial.IGNORED_WIDGETS from the name hierarchy.
+
+ Example arg tuple added:
+ ("focus", "Activity.Toolbox.Bold")
+ Side effects:
+ -Handlers connected on the various objects
+ -Handler ID's stored in self.handlers
+
+ @param object the object to recurse on
+ @param handler the handler function to connect
+ @param prefix name prepended to the object name to form a chain
+ @param max_depth maximum recursion depth, None for infinity
+ """
+ #Gtk Containers have a get_children() function
+ if hasattr(object,"get_children") and \
+ hasattr(object.get_children,"__call__"):
+ for child in object.get_children():
+ if max_depth is None or max_depth > 0:
+ #Recurse with a prefix on all children
+ pre = ".".join( \
+ [p for p in (prefix, object.get_name()) \
+ if not (p is None or p in Tutorial.IGNORED_WIDGETS)] \
+ )
+ self.register_signals(child,handler,pre,max_depth-1)
+ name = ".".join( \
+ [p for p in (prefix, object.get_name()) \
+ if not (p is None or p in Tutorial.IGNORED_WIDGETS)] \
+ )
+ #register events on the object if a widget XXX necessary to check this?
+ if isinstance(object,gtk.Widget):
+ for sig in Tutorial.EVENTS:
+ try:
+ self.handlers.append( (object,object.connect(sig,handler,(sig, name) )) )
+ except TypeError:
+ continue
+
+