From a188dac0527803edb46eabce04100f1c741a96f3 Mon Sep 17 00:00:00 2001 From: Simon Poirier Date: Sun, 12 Jul 2009 21:34:20 +0000 Subject: repackage of tutorius using distutils --- (limited to 'sugar-toolkit/src/sugar/activity') diff --git a/sugar-toolkit/src/sugar/activity/Makefile.am b/sugar-toolkit/src/sugar/activity/Makefile.am deleted file mode 100644 index 91f6ea8..0000000 --- a/sugar-toolkit/src/sugar/activity/Makefile.am +++ /dev/null @@ -1,10 +0,0 @@ -sugardir = $(pythondir)/sugar/activity -sugar_PYTHON = \ - __init__.py \ - activity.py \ - activityfactory.py \ - activityhandle.py \ - activityservice.py \ - bundlebuilder.py \ - main.py \ - namingalert.py \ No newline at end of file diff --git a/sugar-toolkit/src/sugar/activity/Makefile.in b/sugar-toolkit/src/sugar/activity/Makefile.in deleted file mode 100644 index 24c3dd0..0000000 --- a/sugar-toolkit/src/sugar/activity/Makefile.in +++ /dev/null @@ -1,437 +0,0 @@ -# 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/activity -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/activity -sugar_PYTHON = \ - __init__.py \ - activity.py \ - activityfactory.py \ - activityhandle.py \ - activityservice.py \ - bundlebuilder.py \ - main.py \ - namingalert.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/activity/Makefile'; \ - cd $(top_srcdir) && \ - $(AUTOMAKE) --foreign src/sugar/activity/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/activity/__init__.py b/sugar-toolkit/src/sugar/activity/__init__.py deleted file mode 100644 index 8d3ef2b..0000000 --- a/sugar-toolkit/src/sugar/activity/__init__.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (C) 2006-2007, Red Hat, Inc. -# -# 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 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. - -"""Activity implementation code for Sugar-based activities - -Each activity within the OLPC environment must provide two -dbus services. The first, patterned after the - - sugar.activity.activityfactory.ActivityFactory - -class is responsible for providing a "create" method which -takes a small dictionary with values corresponding to a - - sugar.activity.activityhandle.ActivityHandle - -describing an individual instance of the activity. - -Each activity so registered is described by a - - sugar.activity.bundle.Bundle - -instance, which parses a specially formatted activity.info -file (stored in the activity directory's ./activity -subdirectory). The - - sugar.activity.bundlebuilder - -module provides facilities for the standard setup.py module -which produces and registers bundles from activity source -directories. - -Once instantiated by the ActivityFactory's create method, -each activity must provide an introspection API patterned -after the - - sugar.activity.activityservice.ActivityService - -class. This class allows for querying the ID of the root -window, requesting sharing across the network, and basic -"what type of application are you" queries. -""" diff --git a/sugar-toolkit/src/sugar/activity/__init__py b/sugar-toolkit/src/sugar/activity/__init__py deleted file mode 100644 index e69de29..0000000 --- a/sugar-toolkit/src/sugar/activity/__init__py +++ /dev/null diff --git a/sugar-toolkit/src/sugar/activity/activity.py b/sugar-toolkit/src/sugar/activity/activity.py deleted file mode 100644 index 0ad1d91..0000000 --- a/sugar-toolkit/src/sugar/activity/activity.py +++ /dev/null @@ -1,1102 +0,0 @@ -"""Base class for activities written in Python - -This is currently the only definitive reference for what an -activity must do to participate in the Sugar desktop. - - A Basic Activity - -All activities must implement a class derived from 'Activity' in this class. -The convention is to call it ActivitynameActivity, but this is not required as -the activity.info file associated with your activity will tell the sugar-shell -which class to start. - -For example the most minimal Activity: - - - from sugar.activity import activity - - class ReadActivity(activity.Activity): - pass - -To get a real, working activity, you will at least have to implement: - __init__(), read_file() and write_file() - -Aditionally, you will probably need a at least a Toolbar so you can have some -interesting buttons for the user, like for example 'exit activity' - -See the methods of the Activity class below for more information on what you -will need for a real activity. - -STABLE. -""" -# Copyright (C) 2006-2007 Red Hat, Inc. -# Copyright (C) 2007-2009 One Laptop Per Child -# -# 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 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 gettext -import logging -import os -import time -from hashlib import sha1 -import traceback -import gconf - -import gtk -import gobject -import dbus -import dbus.service -import cjson - -from sugar import util -from sugar.presence import presenceservice -from sugar.activity.activityservice import ActivityService -from sugar.activity.namingalert import NamingAlert -from sugar.graphics import style -from sugar.graphics.window import Window -from sugar.graphics.toolbox import Toolbox -from sugar.graphics.toolbutton import ToolButton -from sugar.graphics.toolcombobox import ToolComboBox -from sugar.graphics.alert import Alert -from sugar.graphics.icon import Icon -from sugar.graphics.xocolor import XoColor -from sugar.datastore import datastore -from sugar.session import XSMPClient -from sugar import wm -from sugar.tutorius.services import ObjectStore - -_ = lambda msg: gettext.dgettext('sugar-toolkit', msg) - -SCOPE_PRIVATE = "private" -SCOPE_INVITE_ONLY = "invite" # shouldn't be shown in UI, it's implicit -SCOPE_NEIGHBORHOOD = "public" - -J_DBUS_SERVICE = 'org.laptop.Journal' -J_DBUS_PATH = '/org/laptop/Journal' -J_DBUS_INTERFACE = 'org.laptop.Journal' - -class ActivityToolbar(gtk.Toolbar): - """The Activity toolbar with the Journal entry title, sharing, - Keep and Stop buttons - - All activities should have this toolbar. It is easiest to add it to your - Activity by using the ActivityToolbox. - """ - def __init__(self, activity): - gtk.Toolbar.__init__(self) - self.set_name("ActivityToolbar") - - self._activity = activity - self._updating_share = False - - activity.connect('shared', self.__activity_shared_cb) - activity.connect('joined', self.__activity_shared_cb) - activity.connect('notify::max_participants', - self.__max_participants_changed_cb) - - if activity.metadata: - self.title = gtk.Entry() - self.title.set_name("Title") - self.title.set_size_request(int(gtk.gdk.screen_width() / 3), -1) - self.title.set_text(activity.metadata['title']) - self.title.connect('changed', self.__title_changed_cb) - self._add_widget(self.title) - - activity.metadata.connect('updated', self.__jobject_updated_cb) - - separator = gtk.SeparatorToolItem() - separator.props.draw = False - separator.set_expand(True) - self.insert(separator, -1) - separator.show() - - if hasattr(self._activity,"get_tutorials") and hasattr(self._activity.get_tutorials,"__call__"): - self.tutorials = ToolComboBox(label_text=_('Tutorials:')) - self.tutorials.combo.connect('changed', self.__tutorial_changed_cb) - tutorials = self._activity.get_tutorials() - self._current_tutorial = None - if tutorials: - for key, tutorial in tutorials.items(): - self.tutorials.combo.append_item(key, tutorial.name) - self.insert(self.tutorials, -1) - self.tutorials.show() - - self.share = ToolComboBox(label_text=_('Share with:')) - self.share.set_name("Share") - self.share.combo.connect('changed', self.__share_changed_cb) - self.share.combo.append_item(SCOPE_PRIVATE, _('Private'), 'zoom-home') - self.share.combo.append_item(SCOPE_NEIGHBORHOOD, _('My Neighborhood'), - 'zoom-neighborhood') - self.insert(self.share, -1) - self.share.show() - - self._update_share() - - self.keep = ToolButton(tooltip=_('Keep')) - self.keep.set_name("Keep") - client = gconf.client_get_default() - color = XoColor(client.get_string('/desktop/sugar/user/color')) - keep_icon = Icon(icon_name='document-save', xo_color=color) - self.keep.set_icon_widget(keep_icon) - keep_icon.show() - self.keep.props.accelerator = 'S' - self.keep.connect('clicked', self.__keep_clicked_cb) - self.insert(self.keep, -1) - self.keep.show() - - self.stop = ToolButton('activity-stop', tooltip=_('Stop')) - self.stop.set_name("Stop") - self.stop.props.accelerator = 'Q' - self.stop.connect('clicked', self.__stop_clicked_cb) - self.insert(self.stop, -1) - self.stop.show() - - self._update_title_sid = None - - - def _update_share(self): - self._updating_share = True - - if self._activity.props.max_participants == 1: - self.share.hide() - - if self._activity.get_shared(): - self.share.set_sensitive(False) - self.share.combo.set_active(1) - else: - self.share.set_sensitive(True) - self.share.combo.set_active(0) - - self._updating_share = False - - def __tutorial_changed_cb(self, combo): - if self._current_tutorial: - self._current_tutorial.detach() - - model = self.tutorials.combo.get_model() - it = self.tutorials.combo.get_active_iter() - (key, ) = model.get(it, 0) - t = self._activity.get_tutorials.get(key,None) - if t: - self._current_tutorial = t - self._current_tutorial.attach(self._activity) - - def __share_changed_cb(self, combo): - if self._updating_share: - return - - model = self.share.combo.get_model() - it = self.share.combo.get_active_iter() - (scope, ) = model.get(it, 0) - if scope == SCOPE_NEIGHBORHOOD: - self._activity.share() - - def __tutorial_changed_cb(self, combo): - """ - Callback for tutorial combobox item change - """ - model = combo.get_model() - it = combo.get_active_iter() - (key, ) = model.get(it, 0) - tutorial = self._activity.get_tutorials().get(key,None) - if not tutorial is None: - if not self._current_tutorial is None: - self._current_tutorial.detach() - self._current_tutorial = tutorial - self._current_tutorial.attach(self._activity) - - def __keep_clicked_cb(self, button): - self._activity.copy() - - def __stop_clicked_cb(self, button): - self._activity.close() - - def __jobject_updated_cb(self, jobject): - self.title.set_text(jobject['title']) - - def __title_changed_cb(self, entry): - if not self._update_title_sid: - self._update_title_sid = gobject.timeout_add_seconds( - 1, self.__update_title_cb) - - def __update_title_cb(self): - title = self.title.get_text() - - self._activity.metadata['title'] = title - self._activity.metadata['title_set_by_user'] = '1' - self._activity.save() - - shared_activity = self._activity.get_shared_activity() - if shared_activity: - shared_activity.props.name = title - - self._update_title_sid = None - return False - - def _add_widget(self, widget, expand=False): - tool_item = gtk.ToolItem() - tool_item.set_expand(expand) - - tool_item.add(widget) - widget.show() - - self.insert(tool_item, -1) - tool_item.show() - - def __activity_shared_cb(self, activity): - self._update_share() - - def __max_participants_changed_cb(self, activity, pspec): - self._update_share() - -class EditToolbar(gtk.Toolbar): - """Provides the standard edit toolbar for Activities. - - Members: - undo -- the undo button - redo -- the redo button - copy -- the copy button - paste -- the paste button - separator -- A separator between undo/redo and copy/paste - - This class only provides the 'edit' buttons in a standard layout, - your activity will need to either hide buttons which make no sense for your - Activity, or you need to connect the button events to your own callbacks: - - ## Example from Read.activity: - # Create the edit toolbar: - self._edit_toolbar = EditToolbar(self._view) - # Hide undo and redo, they're not needed - self._edit_toolbar.undo.props.visible = False - self._edit_toolbar.redo.props.visible = False - # Hide the separator too: - self._edit_toolbar.separator.props.visible = False - - # As long as nothing is selected, copy needs to be insensitive: - self._edit_toolbar.copy.set_sensitive(False) - # When the user clicks the button, call _edit_toolbar_copy_cb() - self._edit_toolbar.copy.connect('clicked', self._edit_toolbar_copy_cb) - - # Add the edit toolbar: - toolbox.add_toolbar(_('Edit'), self._edit_toolbar) - # And make it visible: - self._edit_toolbar.show() - """ - def __init__(self): - gtk.Toolbar.__init__(self) - self.set_name("EditToolbar") - - self.undo = ToolButton('edit-undo') - self.undo.set_name("Undo") - self.undo.set_tooltip(_('Undo')) - self.insert(self.undo, -1) - self.undo.show() - - self.redo = ToolButton('edit-redo') - self.redo.set_name("Redo") - self.redo.set_tooltip(_('Redo')) - self.insert(self.redo, -1) - self.redo.show() - - self.separator = gtk.SeparatorToolItem() - self.separator.set_draw(True) - self.insert(self.separator, -1) - self.separator.show() - - self.copy = ToolButton('edit-copy') - self.copy.set_name("Copy") - self.copy.set_tooltip(_('Copy')) - self.insert(self.copy, -1) - self.copy.show() - - self.paste = ToolButton('edit-paste') - self.paste.set_name("Paste") - self.paste.set_tooltip(_('Paste')) - self.insert(self.paste, -1) - self.paste.show() - -class ActivityToolbox(Toolbox): - """Creates the Toolbox for the Activity - - By default, the toolbox contains only the ActivityToolbar. After creating - the toolbox, you can add your activity specific toolbars, for example the - EditToolbar. - - To add the ActivityToolbox to your Activity in MyActivity.__init__() do: - - # Create the Toolbar with the ActivityToolbar: - toolbox = activity.ActivityToolbox(self) - ... your code, inserting all other toolbars you need, like EditToolbar - - # Add the toolbox to the activity frame: - self.set_toolbox(toolbox) - # And make it visible: - toolbox.show() - """ - def __init__(self, activity): - Toolbox.__init__(self) - - self._activity_toolbar = ActivityToolbar(activity) - self.add_toolbar(_('Activity'), self._activity_toolbar) - self._activity_toolbar.show() - - def get_activity_toolbar(self): - return self._activity_toolbar - -class _ActivitySession(gobject.GObject): - __gsignals__ = { - 'quit-requested': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), - 'quit': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])) - } - - def __init__(self): - gobject.GObject.__init__(self) - - self._xsmp_client = XSMPClient() - self._xsmp_client.connect('quit-requested', self.__sm_quit_requested_cb) - self._xsmp_client.connect('quit', self.__sm_quit_cb) - self._xsmp_client.startup() - - self._activities = [] - self._will_quit = [] - - def register(self, activity): - self._activities.append(activity) - - def unregister(self, activity): - self._activities.remove(activity) - - if len(self._activities) == 0: - logging.debug('Quitting the activity process.') - gtk.main_quit() - - def will_quit(self, activity, will_quit): - if will_quit: - self._will_quit.append(activity) - - # We can quit only when all the instances agreed to - for activity in self._activities: - if activity not in self._will_quit: - return - - self._xsmp_client.will_quit(True) - else: - self._will_quit = [] - self._xsmp_client.will_quit(False) - - def __sm_quit_requested_cb(self, client): - self.emit('quit-requested') - - def __sm_quit_cb(self, client): - self.emit('quit') - -class Activity(Window, gtk.Container): - """This is the base Activity class that all other Activities derive from. - This is where your activity starts. - - To get a working Activity: - 0. Derive your Activity from this class: - class MyActivity(activity.Activity): - ... - - 1. implement an __init__() method for your Activity class. - - Use your init method to create your own ActivityToolbar which will - contain some standard buttons: - toolbox = activity.ActivityToolbox(self) - - Add extra Toolbars to your toolbox. - - You should setup Activity sharing here too. - - Finaly, your Activity may need some resources which you can claim - here too. - - The __init__() method is also used to make the distinction between - being resumed from the Journal, or starting with a blank document. - - 2. Implement read_file() and write_file() - Most activities revolve around creating and storing Journal entries. - For example, Write: You create a document, it is saved to the Journal - and then later you resume working on the document. - - read_file() and write_file() will be called by sugar to tell your - Activity that it should load or save the document the user is working - on. - - 3. Implement our Activity Toolbars. - The Toolbars are added to your Activity in step 1 (the toolbox), but - you need to implement them somewhere. Now is a good time. - - There are a number of standard Toolbars. The most basic one, the one - your almost absolutely MUST have is the ActivityToolbar. Without - this, you're not really making a proper Sugar Activity (which may be - okay, but you should really stop and think about why not!) You do - this with the ActivityToolbox(self) call in step 1. - - Usually, you will also need the standard EditToolbar. This is the one - which has the standard copy and paste buttons. You need to derive - your own EditToolbar class from sugar.EditToolbar: - class EditToolbar(activity.EditToolbar): - ... - - See EditToolbar for the methods you should implement in your class. - - Finaly, your Activity will very likely need some activity specific - buttons and options you can create your own toolbars by deriving a - class from gtk.Toolbar: - class MySpecialToolbar(gtk.Toolbar): - ... - - 4. Use your creativity. Make your Activity something special and share - it with your friends! - - Read through the methods of the Activity class below, to learn more about - how to make an Activity work. - - Hint: A good and simple Activity to learn from is the Read activity. To - create your own activity, you may want to copy it and use it as a template. - """ - __gtype_name__ = 'SugarActivity' - - __gsignals__ = { - 'shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), - 'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])) - } - - def __init__(self, handle, create_jobject=True): - """Initialise the Activity - - handle -- sugar.activity.activityhandle.ActivityHandle - instance providing the activity id and access to the - presence service which *may* provide sharing for this - application - - create_jobject -- boolean - define if it should create a journal object if we are - not resuming - - Side effects: - - Sets the gdk screen DPI setting (resolution) to the - Sugar screen resolution. - - Connects our "destroy" message to our _destroy_cb - method. - - Creates a base gtk.Window within this window. - - Creates an ActivityService (self._bus) servicing - this application. - - Usage: - If your Activity implements __init__(), it should call - the base class __init()__ before doing Activity specific things. - - """ - Window.__init__(self) - - ObjectStore().activity = self - - # process titles will only show 15 characters - # but they get truncated anyway so if more characters - # are supported in the future we will get a better view - # of the processes - proc_title = "%s <%s>" % (get_bundle_name(), handle.activity_id) - util.set_proc_title(proc_title) - - self.connect('realize', self.__realize_cb) - self.connect('delete-event', self.__delete_event_cb) - - self._active = False - self._activity_id = handle.activity_id - self._pservice = presenceservice.get_instance() - self.shared_activity = None - self._share_id = None - self._join_id = None - self._updating_jobject = False - self._closing = False - self._quit_requested = False - self._deleting = False - self._max_participants = 0 - self._invites_queue = [] - self._jobject = None - - self._session = _get_session() - self._session.register(self) - self._session.connect('quit-requested', - self.__session_quit_requested_cb) - self._session.connect('quit', self.__session_quit_cb) - - accel_group = gtk.AccelGroup() - self.set_data('sugar-accel-group', accel_group) - self.add_accel_group(accel_group) - - self._bus = ActivityService(self) - self._owns_file = False - - share_scope = SCOPE_PRIVATE - - if handle.object_id: - self._jobject = datastore.get(handle.object_id) - self.set_title(self._jobject.metadata['title']) - - if self._jobject.metadata.has_key('share-scope'): - share_scope = self._jobject.metadata['share-scope'] - - # handle activity share/join - mesh_instance = self._pservice.get_activity(self._activity_id, - warn_if_none=False) - logging.debug("*** Act %s, mesh instance %r, scope %s", - self._activity_id, mesh_instance, share_scope) - if mesh_instance is not None: - # There's already an instance on the mesh, join it - logging.debug("*** Act %s joining existing mesh instance %r", - self._activity_id, mesh_instance) - self.shared_activity = mesh_instance - self.shared_activity.connect('notify::private', - self.__privacy_changed_cb) - self._join_id = self.shared_activity.connect("joined", - self.__joined_cb) - if not self.shared_activity.props.joined: - self.shared_activity.join() - else: - self.__joined_cb(self.shared_activity, True, None) - elif share_scope != SCOPE_PRIVATE: - logging.debug("*** Act %s no existing mesh instance, but used to " \ - "be shared, will share" % self._activity_id) - # no existing mesh instance, but activity used to be shared, so - # restart the share - if share_scope == SCOPE_INVITE_ONLY: - self.share(private=True) - elif share_scope == SCOPE_NEIGHBORHOOD: - self.share(private=False) - else: - logging.debug("Unknown share scope %r" % share_scope) - - if handle.object_id is None and create_jobject: - logging.debug('Creating a jobject.') - self._jobject = datastore.create() - title = _('%s Activity') % get_bundle_name() - self._jobject.metadata['title'] = title - self.set_title(self._jobject.metadata['title']) - self._jobject.metadata['title_set_by_user'] = '0' - self._jobject.metadata['activity'] = self.get_bundle_id() - self._jobject.metadata['activity_id'] = self.get_id() - self._jobject.metadata['keep'] = '0' - self._jobject.metadata['preview'] = '' - self._jobject.metadata['share-scope'] = SCOPE_PRIVATE - if self.shared_activity is not None: - icon_color = self.shared_activity.props.color - else: - client = gconf.client_get_default() - icon_color = client.get_string('/desktop/sugar/user/color') - self._jobject.metadata['icon-color'] = icon_color - - self._jobject.file_path = '' - # Cannot call datastore.write async for creates: - # https://dev.laptop.org/ticket/3071 - datastore.write(self._jobject) - - def get_active(self): - return self._active - - def set_active(self, active): - if self._active != active: - self._active = active - if not self._active and self._jobject: - self.save() - - active = gobject.property( - type=bool, default=False, getter=get_active, setter=set_active) - - def get_max_participants(self): - return self._max_participants - - def set_max_participants(self, participants): - self._max_participants = participants - - max_participants = gobject.property( - type=int, default=0, getter=get_max_participants, - setter=set_max_participants) - - def get_id(self): - """Returns the activity id of the current instance of your activity. - - The activity id is sort-of-like the unix process id (PID). However, - unlike PIDs it is only different for each new instance (with - create_jobject = True set) and stays the same everytime a user - resumes an activity. This is also the identity of your Activity to other - XOs for use when sharing. - """ - return self._activity_id - - def get_bundle_id(self): - """Returns the bundle_id from the activity.info file""" - return os.environ['SUGAR_BUNDLE_ID'] - - def set_canvas(self, canvas): - """Sets the 'work area' of your activity with the canvas of your choice. - - One commonly used canvas is gtk.ScrolledWindow - """ - Window.set_canvas(self, canvas) - canvas.connect('map', self.__canvas_map_cb) - - def __session_quit_requested_cb(self, session): - self._quit_requested = True - - if not self._prepare_close(): - session.will_quit(self, False) - elif not self._updating_jobject: - session.will_quit(self, True) - - def __session_quit_cb(self, client): - self._complete_close() - - def __canvas_map_cb(self, canvas): - if self._jobject and self._jobject.file_path: - self.read_file(self._jobject.file_path) - - def __jobject_create_cb(self): - pass - - def __jobject_error_cb(self, err): - logging.debug("Error creating activity datastore object: %s" % err) - - def get_activity_root(self): - """ FIXME: Deprecated. This part of the API has been moved - out of this class to the module itself - - Returns a path for saving Activity specific preferences, etc. - - Returns a path to the location in the filesystem where the activity can - store activity related data that doesn't pertain to the current - execution of the activity and thus cannot go into the DataStore. - - Currently, this will return something like - ~/.sugar/default/MyActivityName/ - - Activities should ONLY save settings, user preferences and other data - which isn't specific to a journal item here. If (meta-)data is in anyway - specific to a journal entry, it MUST be stored in the DataStore. - """ - if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \ - os.environ['SUGAR_ACTIVITY_ROOT']: - return os.environ['SUGAR_ACTIVITY_ROOT'] - else: - return '/' - - def read_file(self, file_path): - """ - Subclasses implement this method if they support resuming objects from - the journal. 'file_path' is the file to read from. - - You should immediately open the file from the file_path, because the - file_name will be deleted immediately after returning from read_file(). - Once the file has been opened, you do not have to read it immediately: - After you have opened it, the file will only be really gone when you - close it. - - Although not required, this is also a good time to read all meta-data: - the file itself cannot be changed externally, but the title, description - and other metadata['tags'] may change. So if it is important for you to - notice changes, this is the time to record the originals. - """ - raise NotImplementedError - - def write_file(self, file_path): - """ - Subclasses implement this method if they support saving data to objects - in the journal. 'file_path' is the file to write to. - - If the user did make changes, you should create the file_path and save - all document data to it. - - Additionally, you should also write any metadata needed to resume your - activity. For example, the Read activity saves the current page and zoom - level, so it can display the page. - - Note: Currently, the file_path *WILL* be different from the one you - received in file_read(). Even if you kept the file_path from file_read() - open until now, you must still write the entire file to this file_path. - """ - raise NotImplementedError - - def __save_cb(self): - logging.debug('Activity.__save_cb') - self._updating_jobject = False - if self._quit_requested: - self._session.will_quit(self, True) - elif self._closing: - self._complete_close() - - def __save_error_cb(self, err): - logging.debug('Activity.__save_error_cb') - self._updating_jobject = False - if self._quit_requested: - self._session.will_quit(self, False) - if self._closing: - self._show_keep_failed_dialog() - self._closing = False - logging.debug("Error saving activity object to datastore: %s" % err) - - def _cleanup_jobject(self): - if self._jobject: - if self._owns_file and os.path.isfile(self._jobject.file_path): - logging.debug('_cleanup_jobject: removing %r' % - self._jobject.file_path) - os.remove(self._jobject.file_path) - self._owns_file = False - self._jobject.destroy() - self._jobject = None - - def _get_preview(self): - - if self.canvas is None or not hasattr(self.canvas, 'get_snapshot'): - return None - pixmap = self.canvas.get_snapshot((-1, -1, 0, 0)) - - width, height = pixmap.get_size() - pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height) - pixbuf = pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), - 0, 0, 0, 0, width, height) - pixbuf = pixbuf.scale_simple(style.zoom(300), style.zoom(225), - gtk.gdk.INTERP_BILINEAR) - - preview_data = [] - def save_func(buf, data): - data.append(buf) - - pixbuf.save_to_callback(save_func, 'png', user_data=preview_data) - preview_data = ''.join(preview_data) - - return preview_data - - def _get_buddies(self): - if self.shared_activity is not None: - buddies = {} - for buddy in self.shared_activity.get_joined_buddies(): - if not buddy.props.owner: - buddy_id = sha1(buddy.props.key).hexdigest() - buddies[buddy_id] = [buddy.props.nick, buddy.props.color] - return buddies - else: - return {} - - def save(self): - """Request that the activity is saved to the Journal. - - This method is called by the close() method below. In general, - activities should not override this method. This method is part of the - public API of an Acivity, and should behave in standard ways. Use your - own implementation of write_file() to save your Activity specific data. - """ - - if self._jobject is None: - logging.debug('Cannot save, no journal object.') - return - - logging.debug('Activity.save: %r' % self._jobject.object_id) - - if self._updating_jobject: - logging.info('Activity.save: still processing a previous request.') - return - - buddies_dict = self._get_buddies() - if buddies_dict: - self.metadata['buddies_id'] = cjson.encode(buddies_dict.keys()) - self.metadata['buddies'] = cjson.encode(self._get_buddies()) - - preview = self._get_preview() - if preview is not None: - self.metadata['preview'] = dbus.ByteArray(preview) - - try: - file_path = os.path.join(self.get_activity_root(), 'instance', - '%i' % time.time()) - self.write_file(file_path) - self._owns_file = True - self._jobject.file_path = file_path - except NotImplementedError: - logging.debug('Activity.write_file is not implemented.') - - # Cannot call datastore.write async for creates: - # https://dev.laptop.org/ticket/3071 - if self._jobject.object_id is None: - datastore.write(self._jobject, transfer_ownership=True) - else: - self._updating_jobject = True - datastore.write(self._jobject, - transfer_ownership=True, - reply_handler=self.__save_cb, - error_handler=self.__save_error_cb) - - def copy(self): - """Request that the activity 'Keep in Journal' the current state - of the activity. - - Activities should not override this method. Instead, like save() do any - copy work that needs to be done in write_file() - """ - logging.debug('Activity.copy: %r' % self._jobject.object_id) - self.save() - self._jobject.object_id = None - - def __privacy_changed_cb(self, shared_activity, param_spec): - if shared_activity.props.private: - self._jobject.metadata['share-scope'] = SCOPE_INVITE_ONLY - else: - self._jobject.metadata['share-scope'] = SCOPE_NEIGHBORHOOD - - def __joined_cb(self, activity, success, err): - """Callback when join has finished""" - self.shared_activity.disconnect(self._join_id) - self._join_id = None - if not success: - logging.debug("Failed to join activity: %s" % err) - return - - self.present() - self.emit('joined') - self.__privacy_changed_cb(self.shared_activity, None) - - def get_shared_activity(self): - """Returns an instance of the shared Activity or None - - The shared activity is of type sugar.presence.activity.Activity - """ - return self._shared_activity - - def get_shared(self): - """Returns TRUE if the activity is shared on the mesh.""" - if not self.shared_activity: - return False - return self.shared_activity.props.joined - - def __share_cb(self, ps, success, activity, err): - self._pservice.disconnect(self._share_id) - self._share_id = None - if not success: - logging.debug('Share of activity %s failed: %s.' % - (self._activity_id, err)) - return - - logging.debug('Share of activity %s successful, PS activity is %r.', - self._activity_id, activity) - - activity.props.name = self._jobject.metadata['title'] - - self.shared_activity = activity - self.shared_activity.connect('notify::private', - self.__privacy_changed_cb) - self.emit('shared') - self.__privacy_changed_cb(self.shared_activity, None) - - self._send_invites() - - def _invite_response_cb(self, error): - if error: - logging.error('Invite failed: %s' % error) - - def _send_invites(self): - while self._invites_queue: - buddy_key = self._invites_queue.pop() - buddy = self._pservice.get_buddy(buddy_key) - if buddy: - self.shared_activity.invite( - buddy, '', self._invite_response_cb) - else: - logging.error('Cannot invite %s, no such buddy.' % buddy_key) - - def invite(self, buddy_key): - """Invite a buddy to join this Activity. - - Side Effects: - Calls self.share(True) to privately share the activity if it wasn't - shared before. - """ - self._invites_queue.append(buddy_key) - - if (self.shared_activity is None - or not self.shared_activity.props.joined): - self.share(True) - else: - self._send_invites() - - def share(self, private=False): - """Request that the activity be shared on the network. - - private -- bool: True to share by invitation only, - False to advertise as shared to everyone. - - Once the activity is shared, its privacy can be changed by setting - its 'private' property. - """ - if self.shared_activity and self.shared_activity.props.joined: - raise RuntimeError("Activity %s already shared." % - self._activity_id) - verb = private and 'private' or 'public' - logging.debug('Requesting %s share of activity %s.' % - (verb, self._activity_id)) - self._share_id = self._pservice.connect("activity-shared", - self.__share_cb) - self._pservice.share_activity(self, private=private) - - def _show_keep_failed_dialog(self): - alert = Alert() - alert.props.title = _('Keep error') - alert.props.msg = _('Keep error: all changes will be lost') - - cancel_icon = Icon(icon_name='dialog-cancel') - alert.add_button(gtk.RESPONSE_CANCEL, _('Don\'t stop'), cancel_icon) - - stop_icon = Icon(icon_name='dialog-ok') - alert.add_button(gtk.RESPONSE_OK, _('Stop anyway'), stop_icon) - - self.add_alert(alert) - alert.connect('response', self._keep_failed_dialog_response_cb) - - self.present() - - def _keep_failed_dialog_response_cb(self, alert, response_id): - self.remove_alert(alert) - if response_id == gtk.RESPONSE_OK: - self.close(skip_save=True) - - def can_close(self): - """Activities should override this function if they want to perform - extra checks before actually closing.""" - - return True - - def _prepare_close(self, skip_save=False): - if not skip_save: - try: - self.save() - except Exception: - logging.info(traceback.format_exc()) - self._show_keep_failed_dialog() - return False - - if self.shared_activity: - self.shared_activity.leave() - - self._closing = True - - return True - - def _complete_close(self): - self._cleanup_jobject() - self.destroy() - - # Make the exported object inaccessible - dbus.service.Object.remove_from_connection(self._bus) - - self._session.unregister(self) - - def close(self, skip_save=False): - """Request that the activity be stopped and saved to the Journal - - Activities should not override this method, but should implement - write_file() to do any state saving instead. If the application wants - to control wether it can close, it should override can_close(). - """ - if not self.can_close(): - return - - if skip_save or self.metadata.get('title_set_by_user', '0') == '1': - if not self._closing: - if not self._prepare_close(skip_save): - return - - if not self._updating_jobject: - self._complete_close() - else: - title_alert = NamingAlert(self, get_bundle_path()) - title_alert.set_transient_for(self.get_toplevel()) - title_alert.show() - - def __realize_cb(self, window): - wm.set_bundle_id(window.window, self.get_bundle_id()) - wm.set_activity_id(window.window, str(self._activity_id)) - - def __delete_event_cb(self, widget, event): - self.close() - return True - - def get_metadata(self): - """Returns the jobject metadata or None if there is no jobject. - - Activities can set metadata in write_file() using: - self.metadata['MyKey'] = "Something" - - and retrieve metadata in read_file() using: - self.metadata.get('MyKey', 'aDefaultValue') - - Note: Make sure your activity works properly if one or more of the - metadata items is missing. Never assume they will all be present. - """ - if self._jobject: - return self._jobject.metadata - else: - return None - - metadata = property(get_metadata, None) - - def handle_view_source(self): - raise NotImplementedError - - def get_document_path(self, async_cb, async_err_cb): - async_err_cb(NotImplementedError()) - - def get_tutorials(self): - return {} - - # DEPRECATED - _shared_activity = property(lambda self: self.shared_activity, None) - -_session = None - -def _get_session(): - global _session - - if _session is None: - _session = _ActivitySession() - - return _session - -def get_bundle_name(): - """Return the bundle name for the current process' bundle""" - return os.environ['SUGAR_BUNDLE_NAME'] - -def get_bundle_path(): - """Return the bundle path for the current process' bundle""" - return os.environ['SUGAR_BUNDLE_PATH'] - -def get_activity_root(): - """Returns a path for saving Activity specific preferences, etc.""" - if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \ - os.environ['SUGAR_ACTIVITY_ROOT']: - return os.environ['SUGAR_ACTIVITY_ROOT'] - else: - raise RuntimeError("No SUGAR_ACTIVITY_ROOT set.") - -def show_object_in_journal(object_id): - bus = dbus.SessionBus() - obj = bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH) - journal = dbus.Interface(obj, J_DBUS_INTERFACE) - journal.ShowObject(object_id) diff --git a/sugar-toolkit/src/sugar/activity/activityfactory.py b/sugar-toolkit/src/sugar/activity/activityfactory.py deleted file mode 100644 index e92314d..0000000 --- a/sugar-toolkit/src/sugar/activity/activityfactory.py +++ /dev/null @@ -1,343 +0,0 @@ -# Copyright (C) 2006-2007 Red Hat, Inc. -# -# 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 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. - -"""Shell side object which manages request to start activity - -UNSTABLE. Activities are currently not allowed to run other activities so at -the moment there is no reason to stabilize this API. -""" - -import logging - -import dbus -import gobject - -from sugar.presence import presenceservice -from sugar.activity.activityhandle import ActivityHandle -from sugar import util -from sugar import env - -from errno import EEXIST, ENOSPC - -import os - -_SHELL_SERVICE = "org.laptop.Shell" -_SHELL_PATH = "/org/laptop/Shell" -_SHELL_IFACE = "org.laptop.Shell" - -_DS_SERVICE = "org.laptop.sugar.DataStore" -_DS_INTERFACE = "org.laptop.sugar.DataStore" -_DS_PATH = "/org/laptop/sugar/DataStore" - -_ACTIVITY_FACTORY_INTERFACE = "org.laptop.ActivityFactory" - -_RAINBOW_SERVICE_NAME = "org.laptop.security.Rainbow" -_RAINBOW_ACTIVITY_FACTORY_PATH = "/" -_RAINBOW_ACTIVITY_FACTORY_INTERFACE = "org.laptop.security.Rainbow" - -# helper method to close all filedescriptors -# borrowed from subprocess.py -try: - MAXFD = os.sysconf("SC_OPEN_MAX") -except ValueError: - MAXFD = 256 -def _close_fds(): - for i in xrange(3, MAXFD): - try: - os.close(i) - # pylint: disable-msg=W0704 - except Exception: - pass - -def create_activity_id(): - """Generate a new, unique ID for this activity""" - pservice = presenceservice.get_instance() - - # create a new unique activity ID - i = 0 - act_id = None - while i < 10: - act_id = util.unique_id() - i += 1 - - # check through network activities - found = False - activities = pservice.get_activities() - for act in activities: - if act_id == act.props.id: - found = True - break - if not found: - return act_id - raise RuntimeError("Cannot generate unique activity id.") - -def get_environment(activity): - environ = os.environ.copy() - - bin_path = os.path.join(activity.get_path(), 'bin') - - activity_root = env.get_profile_path(activity.get_bundle_id()) - if not os.path.exists(activity_root): - os.mkdir(activity_root) - - data_dir = os.path.join(activity_root, 'instance') - if not os.path.exists(data_dir): - os.mkdir(data_dir) - - data_dir = os.path.join(activity_root, 'data') - if not os.path.exists(data_dir): - os.mkdir(data_dir) - - tmp_dir = os.path.join(activity_root, 'tmp') - if not os.path.exists(tmp_dir): - os.mkdir(tmp_dir) - - environ['SUGAR_BUNDLE_PATH'] = activity.get_path() - environ['SUGAR_BUNDLE_ID'] = activity.get_bundle_id() - environ['SUGAR_ACTIVITY_ROOT'] = activity_root - environ['PATH'] = bin_path + ':' + environ['PATH'] - #environ['RAINBOW_STRACE_LOG'] = '1' - - if activity.get_path().startswith(env.get_user_activities_path()): - environ['SUGAR_LOCALEDIR'] = os.path.join(activity.get_path(), 'locale') - - if activity.get_bundle_id() in [ 'org.laptop.WebActivity', - 'org.laptop.GmailActivity', - 'org.laptop.WikiBrowseActivity' - ]: - environ['RAINBOW_CONSTANT_UID'] = 'yes' - - return environ - -def get_command(activity, activity_id=None, object_id=None, uri=None): - if not activity_id: - activity_id = create_activity_id() - - command = activity.get_command().split(' ') - command.extend(['-b', activity.get_bundle_id()]) - command.extend(['-a', activity_id]) - - if object_id is not None: - command.extend(['-o', object_id]) - if uri is not None: - command.extend(['-u', uri]) - - # if the command is in $BUNDLE_ROOT/bin, execute the absolute path so there - # is no need to mangle with the shell's PATH - if '/' not in command[0]: - bin_path = os.path.join(activity.get_path(), 'bin') - absolute_path = os.path.join(bin_path, command[0]) - if os.path.exists(absolute_path): - command[0] = absolute_path - - logging.debug('launching: %r' % command) - - return command - -def open_log_file(activity): - i = 1 - while True: - path = env.get_logs_path('%s-%s.log' % (activity.get_bundle_id(), i)) - try: - fd = os.open(path, os.O_EXCL | os.O_CREAT \ - | os.O_SYNC | os.O_WRONLY, 0644) - f = os.fdopen(fd, 'w', 0) - return (path, f) - except OSError, e: - if e.errno == EEXIST: - i += 1 - elif e.errno == ENOSPC: - # not the end of the world; let's try to keep going. - return ('/dev/null', open('/dev/null','w')) - else: - raise e - -class ActivityCreationHandler(gobject.GObject): - """Sugar-side activity creation interface - - This object uses a dbus method on the ActivityFactory - service to create the new activity. It generates - GObject events in response to the success/failure of - activity startup using callbacks to the service's - create call. - """ - - def __init__(self, bundle, handle): - """Initialise the handler - - bundle -- the ActivityBundle to launch - activity_handle -- stores the values which are to - be passed to the service to uniquely identify - the activity to be created and the sharing - service that may or may not be connected with it - - sugar.activity.activityhandle.ActivityHandle instance - - calls the "create" method on the service for this - particular activity type and registers the - _reply_handler and _error_handler methods on that - call's results. - - The specific service which creates new instances of this - particular type of activity is created during the activity - registration process in shell bundle registry which creates - service definition files for each registered bundle type. - - If the file '/etc/olpc-security' exists, then activity launching - will be delegated to the prototype 'Rainbow' security service. - """ - gobject.GObject.__init__(self) - - self._bundle = bundle - self._service_name = bundle.get_bundle_id() - self._handle = handle - - self._use_rainbow = os.path.exists('/etc/olpc-security') - if self._service_name in [ 'org.laptop.JournalActivity', - 'org.laptop.Terminal', - 'org.laptop.Log', - 'org.laptop.Analyze' - ]: - self._use_rainbow = False - - bus = dbus.SessionBus() - - bus_object = bus.get_object(_SHELL_SERVICE, _SHELL_PATH) - self._shell = dbus.Interface(bus_object, _SHELL_IFACE) - - if handle.activity_id is not None and \ - handle.object_id is None: - datastore = dbus.Interface( - bus.get_object(_DS_SERVICE, _DS_PATH), _DS_INTERFACE) - datastore.find({ 'activity_id': self._handle.activity_id }, [], - reply_handler=self._find_object_reply_handler, - error_handler=self._find_object_error_handler, - byte_arrays=True) - else: - self._launch_activity() - - def _launch_activity(self): - if self._handle.activity_id != None: - self._shell.ActivateActivity(self._handle.activity_id, - reply_handler=self._activate_reply_handler, - error_handler=self._activate_error_handler) - else: - self._create_activity() - - def _create_activity(self): - if self._handle.activity_id is None: - self._handle.activity_id = create_activity_id() - - self._shell.NotifyLaunch( - self._service_name, self._handle.activity_id, - reply_handler=self._no_reply_handler, - error_handler=self._notify_launch_error_handler) - - environ = get_environment(self._bundle) - (log_path, log_file) = open_log_file(self._bundle) - command = get_command(self._bundle, self._handle.activity_id, - self._handle.object_id, - self._handle.uri) - - if not self._use_rainbow: - # use gobject spawn functionality, so that zombies are - # automatically reaped by the gobject event loop. - def child_setup(): - # clone logfile.fileno() onto stdout/stderr - os.dup2(log_file.fileno(), 1) - os.dup2(log_file.fileno(), 2) - # close all other fds - _close_fds() - # we need to sanitize and str-ize the various bits which - # dbus gives us. - gobject.spawn_async([str(s) for s in command], - envp=['%s=%s' % (k, str(v)) - for k, v in environ.items()], - working_directory=str(self._bundle.get_path()), - child_setup=child_setup, - flags=(gobject.SPAWN_SEARCH_PATH | - gobject.SPAWN_LEAVE_DESCRIPTORS_OPEN)) - log_file.close() - else: - log_file.close() - system_bus = dbus.SystemBus() - factory = system_bus.get_object(_RAINBOW_SERVICE_NAME, - _RAINBOW_ACTIVITY_FACTORY_PATH) - factory.CreateActivity( - log_path, - environ, - command, - environ['SUGAR_BUNDLE_PATH'], - environ['SUGAR_BUNDLE_ID'], - timeout=30, - reply_handler=self._create_reply_handler, - error_handler=self._create_error_handler, - dbus_interface=_RAINBOW_ACTIVITY_FACTORY_INTERFACE) - - def _no_reply_handler(self, *args): - pass - - def _notify_launch_failure_error_handler(self, err): - logging.error('Notify launch failure failed %s' % err) - - def _notify_launch_error_handler(self, err): - logging.debug('Notify launch failed %s' % err) - - def _activate_reply_handler(self, activated): - if not activated: - self._create_activity() - - def _activate_error_handler(self, err): - logging.error("Activity activation request failed %s" % err) - - def _create_reply_handler(self): - logging.debug("Activity created %s (%s)." % - (self._handle.activity_id, self._service_name)) - - def _create_error_handler(self, err): - logging.error("Couldn't create activity %s (%s): %s" % - (self._handle.activity_id, self._service_name, err)) - self._shell.NotifyLaunchFailure( - self._handle.activity_id, reply_handler=self._no_reply_handler, - error_handler=self._notify_launch_failure_error_handler) - - def _find_object_reply_handler(self, jobjects, count): - if count > 0: - if count > 1: - logging.debug("Multiple objects has the same activity_id.") - self._handle.object_id = jobjects[0]['uid'] - self._create_activity() - - def _find_object_error_handler(self, err): - logging.error("Datastore find failed %s" % err) - self._create_activity() - -def create(bundle, activity_handle=None): - """Create a new activity from its name.""" - if not activity_handle: - activity_handle = ActivityHandle() - return ActivityCreationHandler(bundle, activity_handle) - -def create_with_uri(bundle, uri): - """Create a new activity and pass the uri as handle.""" - activity_handle = ActivityHandle(uri=uri) - return ActivityCreationHandler(bundle, activity_handle) - -def create_with_object_id(bundle, object_id): - """Create a new activity and pass the object id as handle.""" - activity_handle = ActivityHandle(object_id=object_id) - return ActivityCreationHandler(bundle, activity_handle) diff --git a/sugar-toolkit/src/sugar/activity/activityhandle.py b/sugar-toolkit/src/sugar/activity/activityhandle.py deleted file mode 100644 index f255fd5..0000000 --- a/sugar-toolkit/src/sugar/activity/activityhandle.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright (C) 2006-2007 Red Hat, Inc. -# -# 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 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. - -""" -STABLE. -""" - -class ActivityHandle(object): - """Data structure storing simple activity metadata""" - def __init__( - self, activity_id=None, object_id=None, uri=None - ): - """Initialise the handle from activity_id - - activity_id -- unique id for the activity to be - created - object_id -- identity of the journal object - associated with the activity. It was used by - the journal prototype implementation, might - change when we do the real one. - - When you resume an activity from the journal - the object_id will be passed in. It's optional - since new activities does not have an - associated object (yet). - - XXX Not clear how this relates to the activity - id yet, i.e. not sure we really need both. TBF - uri -- URI associated with the activity. Used when - opening an external file or resource in the - activity, rather than a journal object - (downloads stored on the file system for - example or web pages) - """ - self.activity_id = activity_id - self.object_id = object_id - self.uri = uri - - def get_dict(self): - """Retrieve our settings as a dictionary""" - result = { 'activity_id' : self.activity_id } - if self.object_id: - result['object_id'] = self.object_id - if self.uri: - result['uri'] = self.uri - - return result - -def create_from_dict(handle_dict): - """Create a handle from a dictionary of parameters""" - result = ActivityHandle( - handle_dict['activity_id'], - object_id = handle_dict.get('object_id'), - uri = handle_dict.get('uri'), - ) - return result diff --git a/sugar-toolkit/src/sugar/activity/activityservice.py b/sugar-toolkit/src/sugar/activity/activityservice.py deleted file mode 100644 index ff806f3..0000000 --- a/sugar-toolkit/src/sugar/activity/activityservice.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright (C) 2006-2007 Red Hat, Inc. -# -# 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 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. - -""" -UNSTABLE. It should really be internal to the Activity class. -""" - -import logging - -import dbus -import dbus.service - -_ACTIVITY_SERVICE_NAME = "org.laptop.Activity" -_ACTIVITY_SERVICE_PATH = "/org/laptop/Activity" -_ACTIVITY_INTERFACE = "org.laptop.Activity" - -class ActivityService(dbus.service.Object): - """Base dbus service object that each Activity uses to export dbus methods. - - The dbus service is separate from the actual Activity object so that we can - tightly control what stuff passes through the dbus python bindings.""" - - def __init__(self, activity): - """Initialise the service for the given activity - - activity -- sugar.activity.activity.Activity instance - - Creates dbus services that use the instance's activity_id - as discriminants among all active services - of this type. That is, the services are all available - as names/paths derived from the instance's activity_id. - - The various methods exposed on dbus are just forwarded - to the client Activity object's equally-named methods. - """ - activity.realize() - - activity_id = activity.get_id() - service_name = _ACTIVITY_SERVICE_NAME + activity_id - object_path = _ACTIVITY_SERVICE_PATH + "/" + activity_id - - bus = dbus.SessionBus() - bus_name = dbus.service.BusName(service_name, bus=bus) - dbus.service.Object.__init__(self, bus_name, object_path) - - self._activity = activity - - @dbus.service.method(_ACTIVITY_INTERFACE) - def SetActive(self, active): - logging.debug('ActivityService.set_active: %s.' % active) - self._activity.props.active = active - - @dbus.service.method(_ACTIVITY_INTERFACE) - def Invite(self, buddy_key): - self._activity.invite(buddy_key) - - @dbus.service.method(_ACTIVITY_INTERFACE) - def HandleViewSource(self): - self._activity.handle_view_source() - - @dbus.service.method(_ACTIVITY_INTERFACE, - async_callbacks=('async_cb', 'async_err_cb')) - def GetDocumentPath(self, async_cb, async_err_cb): - try: - self._activity.get_document_path(async_cb, async_err_cb) - except Exception, e: - async_err_cb(e) - diff --git a/sugar-toolkit/src/sugar/activity/bundlebuilder.py b/sugar-toolkit/src/sugar/activity/bundlebuilder.py deleted file mode 100644 index ab3679b..0000000 --- a/sugar-toolkit/src/sugar/activity/bundlebuilder.py +++ /dev/null @@ -1,398 +0,0 @@ -# Copyright (C) 2008 Red Hat, Inc. -# -# 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 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. - -""" -STABLE. -""" - -import os -import sys -import zipfile -import tarfile -import shutil -import subprocess -import re -import gettext -from optparse import OptionParser -import logging -from fnmatch import fnmatch - -from sugar import env -from sugar.bundle.activitybundle import ActivityBundle - -IGNORE_DIRS = ['dist', '.git'] -IGNORE_FILES = ['.gitignore', 'MANIFEST', '*.pyc', '*~', '*.bak', 'pseudo.po'] - -def list_files(base_dir, ignore_dirs=None, ignore_files=None): - result = [] - - for root, dirs, files in os.walk(base_dir): - if ignore_files: - for pattern in ignore_files: - files = [f for f in files if not fnmatch(f, pattern)] - - rel_path = root[len(base_dir) + 1:] - for f in files: - result.append(os.path.join(rel_path, f)) - - if ignore_dirs and root == base_dir: - for ignore in ignore_dirs: - if ignore in dirs: - dirs.remove(ignore) - - return result - -class Config(object): - def __init__(self, source_dir=None, dist_dir = None, dist_name = None): - self.source_dir = source_dir or os.getcwd() - self.dist_dir = dist_dir or os.path.join(self.source_dir, 'dist') - self.dist_name = dist_name - self.bundle = None - self.version = None - self.activity_name = None - self.bundle_id = None - self.bundle_name = None - self.bundle_root_dir = None - self.tar_root_dir = None - self.xo_name = None - self.tar_name = None - - self.update() - - def update(self): - self.bundle = bundle = ActivityBundle(self.source_dir) - self.version = bundle.get_activity_version() - self.activity_name = bundle.get_name() - self.bundle_id = bundle.get_bundle_id() - self.bundle_name = reduce(lambda x, y:x+y, self.activity_name.split()) - self.bundle_root_dir = self.bundle_name + '.activity' - self.tar_root_dir = '%s-%d' % (self.bundle_name, self.version) - - if self.dist_name: - self.xo_name = self.tar_name = self.dist_name - else: - self.xo_name = '%s-%d.xo' % (self.bundle_name, self.version) - self.tar_name = '%s-%d.tar.bz2' % (self.bundle_name, self.version) - -class Builder(object): - def __init__(self, config): - self.config = config - - def build(self): - self.build_locale() - - def build_locale(self): - po_dir = os.path.join(self.config.source_dir, 'po') - - if not self.config.bundle.is_dir(po_dir): - logging.warn("Missing po/ dir, cannot build_locale") - return - - locale_dir = os.path.join(self.config.source_dir, 'locale') - - if os.path.exists(locale_dir): - shutil.rmtree(locale_dir) - - for f in os.listdir(po_dir): - if not f.endswith('.po') or f == 'pseudo.po': - continue - - file_name = os.path.join(po_dir, f) - lang = f[:-3] - - localedir = os.path.join(self.config.source_dir, 'locale', lang) - mo_path = os.path.join(localedir, 'LC_MESSAGES') - if not os.path.isdir(mo_path): - os.makedirs(mo_path) - - mo_file = os.path.join(mo_path, "%s.mo" % self.config.bundle_id) - args = ["msgfmt", "--output-file=%s" % mo_file, file_name] - retcode = subprocess.call(args) - if retcode: - print 'ERROR - msgfmt failed with return code %i.' % retcode - - cat = gettext.GNUTranslations(open(mo_file, 'r')) - translated_name = cat.gettext(self.config.activity_name) - linfo_file = os.path.join(localedir, 'activity.linfo') - f = open(linfo_file, 'w') - f.write('[Activity]\nname = %s\n' % translated_name) - f.close() - - def get_files(self): - files = self.config.bundle.get_files() - - if not files: - logging.error('No files found, fixing the MANIFEST.') - self.fix_manifest() - files = self.config.bundle.get_files() - - return files - - def check_manifest(self): - missing_files = [] - - allfiles = list_files(self.config.source_dir, - IGNORE_DIRS, IGNORE_FILES) - for path in allfiles: - if path not in self.config.bundle.manifest: - missing_files.append(path) - - return missing_files - - def fix_manifest(self): - self.build() - - manifest = self.config.bundle.manifest - - for path in self.check_manifest(): - manifest.append(path) - - f = open(os.path.join(self.config.source_dir, "MANIFEST"), "wb") - for line in manifest: - f.write(line + "\n") - -class Packager(object): - def __init__(self, config): - self.config = config - self.package_path = None - - if not os.path.exists(self.config.dist_dir): - os.mkdir(self.config.dist_dir) - -class XOPackager(Packager): - def __init__(self, builder): - Packager.__init__(self, builder.config) - - self.builder = builder - self.package_path = os.path.join(self.config.dist_dir, - self.config.xo_name) - - def package(self): - bundle_zip = zipfile.ZipFile(self.package_path, 'w', - zipfile.ZIP_DEFLATED) - - missing_files = self.builder.check_manifest() - if missing_files: - logging.warn('These files are not included in the manifest ' \ - 'and will not be present in the bundle:\n\n' + - '\n'.join(missing_files) + - '\n\nUse fix_manifest if you want to add them.') - - for f in self.builder.get_files(): - bundle_zip.write(os.path.join(self.config.source_dir, f), - os.path.join(self.config.bundle_root_dir, f)) - - bundle_zip.close() - -class SourcePackager(Packager): - def __init__(self, config): - Packager.__init__(self, config) - self.package_path = os.path.join(self.config.dist_dir, - self.config.tar_name) - - def get_files(self): - git_ls = subprocess.Popen('git-ls-files', stdout=subprocess.PIPE, - cwd=self.config.source_dir) - if git_ls.wait(): - # Fall back to filtered list - return list_files(self.config.source_dir, - IGNORE_DIRS, IGNORE_FILES) - - return [path.strip() for path in git_ls.stdout.readlines()] - - def package(self): - tar = tarfile.open(self.package_path, 'w:bz2') - for f in self.get_files(): - tar.add(os.path.join(self.config.source_dir, f), - os.path.join(self.config.tar_root_dir, f)) - tar.close() - -class Installer(object): - IGNORES = [ 'po/*', 'MANIFEST', 'AUTHORS' ] - - def __init__(self, builder): - self.config = builder.config - self.builder = builder - - def should_ignore(self, f): - for pattern in self.IGNORES: - if fnmatch(f, pattern): - return True - return False - - def install(self, prefix): - self.builder.build() - - activity_path = os.path.join(prefix, 'share', 'sugar', 'activities', - self.config.bundle_root_dir) - - source_to_dest = {} - for f in self.builder.get_files(): - if self.should_ignore(f): - pass - elif f.startswith('locale/') and f.endswith('.mo'): - source_to_dest[f] = os.path.join(prefix, 'share', f) - else: - source_to_dest[f] = os.path.join(activity_path, f) - - for source, dest in source_to_dest.items(): - print 'Install %s to %s.' % (source, dest) - - path = os.path.dirname(dest) - if not os.path.exists(path): - os.makedirs(path) - - shutil.copy(source, dest) - -def cmd_dev(config, args): - '''Setup for development''' - - if args: - print 'Usage: %prog dev' - return - - bundle_path = env.get_user_activities_path() - if not os.path.isdir(bundle_path): - os.mkdir(bundle_path) - bundle_path = os.path.join(bundle_path, config.bundle_root_dir) - try: - os.symlink(config.source_dir, bundle_path) - except OSError: - if os.path.islink(bundle_path): - print 'ERROR - The bundle has been already setup for development.' - else: - print 'ERROR - A bundle with the same name is already installed.' - -def cmd_dist_xo(config, args): - '''Create a xo bundle package''' - - if args: - print 'Usage: %prog dist_xo' - return - - packager = XOPackager(Builder(config)) - packager.package() - -def cmd_fix_manifest(config, args): - '''Add missing files to the manifest''' - - if args: - print 'Usage: %prog fix_manifest' - return - - builder = Builder(config) - builder.fix_manifest() - -def cmd_dist_source(config, args): - '''Create a tar source package''' - - if args: - print 'Usage: %prog dist_source' - return - - packager = SourcePackager(config) - packager.package() - -def cmd_install(config, args): - '''Install the activity in the system''' - - parser = OptionParser(usage='usage: %prog install [options]') - parser.add_option('--prefix', dest='prefix', default=sys.prefix, - help='Prefix to install files to') - (suboptions, subargs) = parser.parse_args(args) - if subargs: - parser.print_help() - return - - installer = Installer(Builder(config)) - installer.install(suboptions.prefix) - -def cmd_genpot(config, args): - '''Generate the gettext pot file''' - - if args: - print 'Usage: %prog genpot' - return - - po_path = os.path.join(config.source_dir, 'po') - if not os.path.isdir(po_path): - os.mkdir(po_path) - - python_files = [] - for root_dummy, dirs_dummy, files in os.walk(config.source_dir): - for file_name in files: - if file_name.endswith('.py'): - python_files.append(file_name) - - # First write out a stub .pot file containing just the translated - # activity name, then have xgettext merge the rest of the - # translations into that. (We can't just append the activity name - # to the end of the .pot file afterwards, because that might - # create a duplicate msgid.) - pot_file = os.path.join('po', '%s.pot' % config.bundle_name) - escaped_name = re.sub('([\\\\"])', '\\\\\\1', config.activity_name) - f = open(pot_file, 'w') - f.write('#: activity/activity.info:2\n') - f.write('msgid "%s"\n' % escaped_name) - f.write('msgstr ""\n') - f.close() - - args = [ 'xgettext', '--join-existing', '--language=Python', - '--keyword=_', '--add-comments=TRANS:', '--output=%s' % pot_file ] - - args += python_files - retcode = subprocess.call(args) - if retcode: - print 'ERROR - xgettext failed with return code %i.' % retcode - -def cmd_build(config, args): - '''Build generated files''' - - if args: - print 'Usage: %prog build' - return - - builder = Builder(config) - builder.build() - -def print_commands(): - print 'Available commands:\n' - - for name, func in globals().items(): - if name.startswith('cmd_'): - print "%-20s %s" % (name.replace('cmd_', ''), func.__doc__) - - print '\n(Type "./setup.py --help" for help about a ' \ - 'particular command\'s options.' - -def start(bundle_name=None): - if bundle_name: - logging.warn("bundle_name deprecated, now comes from activity.info") - - parser = OptionParser(usage='[action] [options]') - parser.disable_interspersed_args() - (options_, args) = parser.parse_args() - - config = Config() - - try: - globals()['cmd_' + args[0]](config, args[1:]) - except (KeyError, IndexError): - print_commands() - -if __name__ == '__main__': - start() diff --git a/sugar-toolkit/src/sugar/activity/main.py b/sugar-toolkit/src/sugar/activity/main.py deleted file mode 100644 index 0295bf9..0000000 --- a/sugar-toolkit/src/sugar/activity/main.py +++ /dev/null @@ -1,140 +0,0 @@ -# Copyright (C) 2008 Red Hat, Inc. -# -# 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 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 gettext -from optparse import OptionParser - -import gtk -import dbus -import dbus.service -import dbus.glib - -from sugar.activity import activityhandle -from sugar.bundle.activitybundle import ActivityBundle -from sugar import logger - -def create_activity_instance(constructor, handle): - activity = constructor(handle) - activity.show() - -def get_single_process_name(bundle_id): - return bundle_id - -def get_single_process_path(bundle_id): - return '/' + bundle_id.replace('.', '/') - -class SingleProcess(dbus.service.Object): - def __init__(self, name_service, constructor): - self.constructor = constructor - - bus = dbus.SessionBus() - bus_name = dbus.service.BusName(name_service, bus=bus) - object_path = get_single_process_path(name_service) - dbus.service.Object.__init__(self, bus_name, object_path) - - @dbus.service.method("org.laptop.SingleProcess", in_signature="a{ss}") - def create(self, handle_dict): - handle = activityhandle.create_from_dict(handle_dict) - create_activity_instance(self.constructor, handle) - -def main(): - parser = OptionParser() - parser.add_option("-b", "--bundle-id", dest="bundle_id", - help="identifier of the activity bundle") - parser.add_option("-a", "--activity-id", dest="activity_id", - help="identifier of the activity instance") - parser.add_option("-o", "--object-id", dest="object_id", - help="identifier of the associated datastore object") - parser.add_option("-u", "--uri", dest="uri", - help="URI to load") - parser.add_option('-s', '--single-process', dest='single_process', - action='store_true', - help='start all the instances in the same process') - (options, args) = parser.parse_args() - - logger.start() - - if 'SUGAR_BUNDLE_PATH' not in os.environ: - print 'SUGAR_BUNDLE_PATH is not defined in the environment.' - sys.exit(1) - - if len(args) == 0: - print 'A python class must be specified as first argument.' - sys.exit(1) - - bundle_path = os.environ['SUGAR_BUNDLE_PATH'] - sys.path.append(bundle_path) - - bundle = ActivityBundle(bundle_path) - - os.environ['SUGAR_BUNDLE_ID'] = bundle.get_bundle_id() - os.environ['SUGAR_BUNDLE_NAME'] = bundle.get_name() - os.environ['SUGAR_BUNDLE_VERSION'] = str(bundle.get_activity_version()) - - gtk.icon_theme_get_default().append_search_path(bundle.get_icons_path()) - - locale_path = None - if 'SUGAR_LOCALEDIR' in os.environ: - locale_path = os.environ['SUGAR_LOCALEDIR'] - - gettext.bindtextdomain(bundle.get_bundle_id(), locale_path) - gettext.textdomain(bundle.get_bundle_id()) - - splitted_module = args[0].rsplit('.', 1) - module_name = splitted_module[0] - class_name = splitted_module[1] - - module = __import__(module_name) - for comp in module_name.split('.')[1:]: - module = getattr(module, comp) - - activity_constructor = getattr(module, class_name) - activity_handle = activityhandle.ActivityHandle( - activity_id=options.activity_id, - object_id=options.object_id, uri=options.uri) - - if options.single_process is True: - sessionbus = dbus.SessionBus() - - service_name = get_single_process_name(options.bundle_id) - service_path = get_single_process_path(options.bundle_id) - - bus_object = sessionbus.get_object( - 'org.freedesktop.DBus', '/org/freedesktop/DBus') - try: - name = bus_object.GetNameOwner( - service_name, dbus_interface='org.freedesktop.DBus') - except dbus.DBusException: - name = None - - if not name: - SingleProcess(service_name, activity_constructor) - else: - single_process = sessionbus.get_object(service_name, service_path) - single_process.create(activity_handle.get_dict()) - - print 'Created %s in a single process.' % service_name - sys.exit(0) - - if hasattr(module, 'start'): - module.start() - - create_activity_instance(activity_constructor, activity_handle) - - gtk.main() diff --git a/sugar-toolkit/src/sugar/activity/namingalert.py b/sugar-toolkit/src/sugar/activity/namingalert.py deleted file mode 100644 index 724d76a..0000000 --- a/sugar-toolkit/src/sugar/activity/namingalert.py +++ /dev/null @@ -1,320 +0,0 @@ -# Copyright (C) 2009 One Laptop Per Child -# -# 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 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 gettext - -import gtk -import gobject -import hippo -import gconf - -from sugar.graphics import style -from sugar.graphics.icon import Icon -from sugar.graphics.xocolor import XoColor -from sugar.graphics.icon import CanvasIcon -from sugar.graphics.entry import CanvasEntry -from sugar.graphics.toolbutton import ToolButton -from sugar.graphics.canvastextview import CanvasTextView - -from sugar.bundle.activitybundle import ActivityBundle - -_ = lambda msg: gettext.dgettext('sugar-toolkit', msg) - -class NamingToolbar(gtk.Toolbar): - """ Toolbar of the naming alert - """ - __gtype_name__ = 'SugarNamingToolbar' - - __gsignals__ = { - 'keep-clicked': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([])) - } - def __init__(self): - gtk.Toolbar.__init__(self) - - client = gconf.client_get_default() - color = XoColor(client.get_string('/desktop/sugar/user/color')) - icon = Icon() - icon.set_from_icon_name('activity-journal', - gtk.ICON_SIZE_LARGE_TOOLBAR) - icon.props.xo_color = color - self._add_widget(icon) - - self._add_separator() - - self._title = gtk.Label(_('Name this entry')) - self._add_widget(self._title) - - self._add_separator(True) - - self._keep_button = ToolButton('dialog-ok', tooltip=_('Keep')) - self._keep_button.props.accelerator = 'Return' - self._keep_button.connect('clicked', self.__keep_button_clicked_cb) - self.insert(self._keep_button, -1) - self._keep_button.show() - - def _add_separator(self, expand=False): - separator = gtk.SeparatorToolItem() - separator.props.draw = False - if expand: - separator.set_expand(True) - else: - separator.set_size_request(style.DEFAULT_SPACING, -1) - self.insert(separator, -1) - separator.show() - - def _add_widget(self, widget, expand=False): - tool_item = gtk.ToolItem() - tool_item.set_expand(expand) - - tool_item.add(widget) - widget.show() - - self.insert(tool_item, -1) - tool_item.show() - - def __keep_button_clicked_cb(self, widget, data=None): - self.emit('keep-clicked') - -class FavoriteIcon(CanvasIcon): - def __init__(self, favorite): - CanvasIcon.__init__(self, icon_name='emblem-favorite', - box_width=style.GRID_CELL_SIZE * 3 / 5, - size=style.SMALL_ICON_SIZE) - self._favorite = None - self.set_favorite(favorite) - self.connect('button-release-event', self.__release_event_cb) - self.connect('motion-notify-event', self.__motion_notify_event_cb) - - def set_favorite(self, favorite): - if favorite == self._favorite: - return - - self._favorite = favorite - if favorite: - client = gconf.client_get_default() - color = XoColor(client.get_string('/desktop/sugar/user/color')) - self.props.xo_color = color - else: - self.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg() - self.props.fill_color = style.COLOR_WHITE.get_svg() - - def get_favorite(self): - return self._favorite - - favorite = gobject.property( - type=bool, default=False, getter=get_favorite, setter=set_favorite) - - def __release_event_cb(self, icon, event): - self.props.favorite = not self.props.favorite - - def __motion_notify_event_cb(self, icon, event): - if not self._favorite: - if event.detail == hippo.MOTION_DETAIL_ENTER: - icon.props.fill_color = style.COLOR_BUTTON_GREY.get_svg() - elif event.detail == hippo.MOTION_DETAIL_LEAVE: - icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg() - -class NamingAlert(gtk.Window): - __gtype_name__ = 'SugarNamingAlert' - - def __init__(self, activity, bundle_path): - gtk.Window.__init__(self) - - self._bundle_path = bundle_path - self._favorite_icon = None - self._title = None - self._description = None - self._tags = None - - accel_group = gtk.AccelGroup() - self.set_data('sugar-accel-group', accel_group) - self.add_accel_group(accel_group) - - self.set_border_width(style.LINE_WIDTH) - offset = style.GRID_CELL_SIZE - width = gtk.gdk.screen_width() - offset * 2 - height = gtk.gdk.screen_height() - offset * 2 - self.set_size_request(width, height) - self.set_position(gtk.WIN_POS_CENTER_ALWAYS) - self.set_decorated(False) - self.set_resizable(False) - self.set_modal(True) - self.connect('realize', self.__realize_cb) - - self._activity = activity - - vbox = gtk.VBox() - self.add(vbox) - vbox.show() - - toolbar = NamingToolbar() - toolbar.connect('keep-clicked', self.__keep_cb) - vbox.pack_start(toolbar, False) - toolbar.show() - - canvas = hippo.Canvas() - self._root = hippo.CanvasBox() - self._root.props.background_color = style.COLOR_WHITE.get_int() - canvas.set_root(self._root) - vbox.pack_start(canvas) - canvas.show() - - body = self._create_body() - self._root.append(body, hippo.PACK_EXPAND) - - widget = self._title.get_property('widget') - widget.grab_focus() - - def _create_body(self): - body = hippo.CanvasBox() - body.props.orientation = hippo.ORIENTATION_VERTICAL - body.props.background_color = style.COLOR_WHITE.get_int() - body.props.padding_top = style.DEFAULT_SPACING * 3 - - header = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL, - padding=style.DEFAULT_PADDING, - padding_right=style.GRID_CELL_SIZE, - spacing=style.DEFAULT_SPACING) - body.append(header) - - descriptions = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL, - spacing=style.DEFAULT_SPACING * 3, - padding_left=style.GRID_CELL_SIZE, - padding_right=style.GRID_CELL_SIZE, - padding_top=style.DEFAULT_SPACING * 3) - - body.append(descriptions, hippo.PACK_EXPAND) - - first_column = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL, - spacing=style.DEFAULT_SPACING) - descriptions.append(first_column) - - second_column = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL, - spacing=style.DEFAULT_SPACING) - descriptions.append(second_column, hippo.PACK_EXPAND) - - self._favorite_icon = self._create_favorite_icon() - header.append(self._favorite_icon) - - activity_icon = self._create_activity_icon() - header.append(activity_icon) - - self._title = self._create_title() - header.append(self._title, hippo.PACK_EXPAND) - - if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL: - header.reverse() - - description_box, self._description = self._create_description() - second_column.append(description_box) - - tags_box, self._tags = self._create_tags() - second_column.append(tags_box) - - return body - - def _create_favorite_icon(self): - favorite_icon = FavoriteIcon(False) - return favorite_icon - - def _create_activity_icon(self): - activity_bundle = ActivityBundle(self._bundle_path) - activity_icon = CanvasIcon(file_name=activity_bundle.get_icon()) - if self._activity.metadata.has_key('icon-color') and \ - self._activity.metadata['icon-color']: - activity_icon.props.xo_color = XoColor( \ - self._activity.metadata['icon-color']) - return activity_icon - - def _create_title(self): - title = CanvasEntry() - title.set_background(style.COLOR_WHITE.get_html()) - title.props.text = self._activity.metadata.get('title', _('Untitled')) - return title - - def _create_description(self): - vbox = hippo.CanvasBox() - vbox.props.spacing = style.DEFAULT_SPACING - - text = hippo.CanvasText(text=_('Description:'), - font_desc=style.FONT_NORMAL.get_pango_desc()) - text.props.color = style.COLOR_BUTTON_GREY.get_int() - - if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL: - text.props.xalign = hippo.ALIGNMENT_END - else: - text.props.xalign = hippo.ALIGNMENT_START - - vbox.append(text) - - description = self._activity.metadata.get('description', '') - text_view = CanvasTextView(description, - box_height=style.GRID_CELL_SIZE * 2) - vbox.append(text_view, hippo.PACK_EXPAND) - - text_view.text_view_widget.props.accepts_tab = False - - return vbox, text_view - - def _create_tags(self): - vbox = hippo.CanvasBox() - vbox.props.spacing = style.DEFAULT_SPACING - - text = hippo.CanvasText(text=_('Tags:'), - font_desc=style.FONT_NORMAL.get_pango_desc()) - text.props.color = style.COLOR_BUTTON_GREY.get_int() - - if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL: - text.props.xalign = hippo.ALIGNMENT_END - else: - text.props.xalign = hippo.ALIGNMENT_START - - vbox.append(text) - - tags = self._activity.metadata.get('tags', '') - text_view = CanvasTextView(tags, box_height=style.GRID_CELL_SIZE * 2) - vbox.append(text_view, hippo.PACK_EXPAND) - - text_view.text_view_widget.props.accepts_tab = False - - return vbox, text_view - - def __realize_cb(self, widget): - self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) - self.window.set_accept_focus(True) - - def __keep_cb(self, widget): - is_favorite = self._favorite_icon.get_favorite() - if is_favorite: - self._activity.metadata['keep'] = 1 - else: - self._activity.metadata['keep'] = 0 - - self._activity.metadata['title'] = self._title.props.text - - new_tags = self._tags.text_view_widget.props.buffer.props.text - self._activity.metadata['tags'] = new_tags - - new_description = \ - self._description.text_view_widget.props.buffer.props.text - self._activity.metadata['description'] = new_description - - self._activity.metadata['title_set_by_user'] = '1' - self._activity.close() - self.destroy() -- cgit v0.9.1