From aa8a5e70c415e6c2acf4ff373d9b366ac4692bb1 Mon Sep 17 00:00:00 2001 From: Pro-Panda Date: Thu, 1 Mar 2018 17:28:56 +0530 Subject: [PATCH] Port from Python 2 to six Signed-off-by: James Cameron --- Makefile.am | 2 +- bin/Makefile.am | 2 +- bin/sugar-activity | 218 +---------------- bin/sugar-activity-web | 4 +- bin/sugar-activity3 | 5 + configure.ac | 2 +- doc/conf.py | 26 +- examples/alert.py | 6 +- examples/animator.py | 2 +- examples/colorbutton.py | 2 +- examples/combobox.py | 2 +- examples/customdestroy.py | 6 +- examples/gtktreesensitive.py | 2 +- examples/iconentry.py | 4 +- examples/radiotoolbutton.py | 2 +- examples/scrollingdetector.py | 6 +- examples/tabs.py | 2 +- examples/toolbutton.py | 2 +- src/sugar3/activity/Makefile.am | 1 + src/sugar3/activity/activity.py | 34 +-- src/sugar3/activity/activityfactory.py | 13 +- src/sugar3/activity/activityhandle.py | 14 +- src/sugar3/activity/activityinstance.py | 222 ++++++++++++++++++ src/sugar3/activity/activityservice.py | 2 +- src/sugar3/activity/bundlebuilder.py | 44 ++-- src/sugar3/activity/webkit1.py | 8 +- src/sugar3/activity/widgets.py | 2 +- src/sugar3/bundle/__init__.py | 6 +- src/sugar3/bundle/activitybundle.py | 4 +- src/sugar3/bundle/bundle.py | 10 +- src/sugar3/bundle/bundleversion.py | 1 + src/sugar3/bundle/contentbundle.py | 7 +- src/sugar3/datastore/datastore.py | 17 +- src/sugar3/dispatch/dispatcher.py | 20 +- src/sugar3/dispatch/saferef.py | 48 ++-- src/sugar3/env.py | 4 +- src/sugar3/graphics/Makefile.am | 1 - src/sugar3/graphics/alert.py | 22 +- src/sugar3/graphics/colorbutton.py | 42 ++-- src/sugar3/graphics/icon.py | 21 +- src/sugar3/graphics/iconentry.py | 2 +- src/sugar3/graphics/objectchooser.py | 24 +- src/sugar3/graphics/palettegroup.py | 2 +- src/sugar3/graphics/palettemenu.py | 2 +- src/sugar3/graphics/palettewindow.py | 14 +- src/sugar3/graphics/popwindow.py | 202 ---------------- src/sugar3/graphics/progressicon.py | 1 + src/sugar3/graphics/scrollingdetector.py | 12 +- src/sugar3/graphics/style.py | 5 +- src/sugar3/graphics/toolbarbox.py | 2 + src/sugar3/graphics/toolbox.py | 14 +- src/sugar3/graphics/toolbutton.py | 2 +- src/sugar3/graphics/tray.py | 4 + src/sugar3/graphics/xocolor.py | 24 +- src/sugar3/logger.py | 25 +- src/sugar3/mime.py | 8 +- src/sugar3/network.py | 24 +- src/sugar3/presence/activity.py | 24 +- src/sugar3/presence/buddy.py | 16 +- src/sugar3/presence/connectionmanager.py | 6 +- src/sugar3/presence/presenceservice.py | 7 +- src/sugar3/presence/tubeconn.py | 7 +- src/sugar3/profile.py | 3 +- src/sugar3/speech.py | 9 +- src/sugar3/test/Makefile.am | 2 +- src/sugar3/test/{unittest.py => _unittest.py} | 10 +- src/sugar3/test/discover.py | 2 - src/sugar3/test/uitree.py | 2 +- src/sugar3/util.py | 20 +- .../sample.activity/activity/activity.info | 2 +- tests/data/sample.activity/setup.py | 2 +- tests/graphics/progressicon.py | 1 + tests/test_mime.py | 2 +- tests/test_uitree.py | 3 +- 74 files changed, 623 insertions(+), 700 deletions(-) create mode 100755 bin/sugar-activity3 create mode 100644 src/sugar3/activity/activityinstance.py delete mode 100644 src/sugar3/graphics/popwindow.py rename src/sugar3/test/{unittest.py => _unittest.py} (93%) diff --git a/Makefile.am b/Makefile.am index 70ed1a8f..d89c4a61 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,4 +26,4 @@ check-po: test: check-po pyflakes $(top_srcdir) pep8 $(top_srcdir) - python -m sugar3.test.discover $(top_srcdir)/tests + python3 -m sugar3.test.discover $(top_srcdir)/tests diff --git a/bin/Makefile.am b/bin/Makefile.am index 2d214b3c..3b5f6f8f 100644 --- a/bin/Makefile.am +++ b/bin/Makefile.am @@ -1 +1 @@ -dist_bin_SCRIPTS = sugar-activity sugar-activity-web +dist_bin_SCRIPTS = sugar-activity sugar-activity-web sugar-activity3 diff --git a/bin/sugar-activity b/bin/sugar-activity index a184655a..9e12d61a 100755 --- a/bin/sugar-activity +++ b/bin/sugar-activity @@ -1,219 +1,5 @@ #!/usr/bin/env python2 -# Copyright (C) 2006-2008, Red Hat, Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +from sugar3.activity import activityinstance -import os -import sys - -# Change the default encoding to avoid UnicodeDecodeError -# http://lists.sugarlabs.org/archive/sugar-devel/2012-August/038928.html -reload(sys) -sys.setdefaultencoding('utf-8') - -import gettext -from optparse import OptionParser - -import dbus -import dbus.service -from dbus.mainloop.glib import DBusGMainLoop -DBusGMainLoop(set_as_default=True) - -from sugar3.activity import activityhandle -from sugar3 import config -from sugar3.bundle.activitybundle import ActivityBundle -from sugar3 import logger - -from sugar3.bundle.bundle import MalformedBundleException - -from distutils.dir_util import mkpath -import time -import hashlib -import random - -def create_activity_instance(constructor, handle): - activity = constructor(handle) - activity.show() - return activity - - -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{sv}') - def create(self, handle_dict): - handle = activityhandle.create_from_dict(handle_dict) - create_activity_instance(self.constructor, handle) - - -def main(): - usage = 'usage: %prog [options] [activity dir] [python class]' - epilog = 'If you are running from a directory containing an Activity, ' \ - 'the argument may be omitted. Otherwise please provide either '\ - 'a directory containing a Sugar Activity [activity dir], a '\ - '[python_class], or both.' - - parser = OptionParser(usage=usage, epilog=epilog) - 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') - parser.add_option('-i', '--invited', dest='invited', - action='store_true', default=False, - help='the activity is being launched for handling an ' - 'invite from the network') - (options, args) = parser.parse_args() - - logger.start() - - activity_class = None - if len(args) == 2: - activity_class = args[1] - os.chdir(args[0]) - elif len(args) == 1: - if os.path.isdir(args[0]): - os.chdir(args[0]) - else: - activity_class = args[0] - - os.environ['SUGAR_BUNDLE_PATH'] = os.path.abspath(os.curdir) - - bundle_path = os.environ['SUGAR_BUNDLE_PATH'] - sys.path.insert(0, bundle_path) - - try: - bundle = ActivityBundle(bundle_path) - except MalformedBundleException: - parser.print_help() - exit(0) - - if not activity_class: - if bundle.get_command().startswith('sugar-activity'): - activity_class = bundle.get_command().split(" ")[1] - - if 'SUGAR_VERSION' not in os.environ: - profile_id = os.environ.get('SUGAR_PROFILE', 'default') - home_dir = os.environ.get('SUGAR_HOME', os.path.expanduser('~/.sugar')) - base = os.path.join(home_dir, profile_id) - activity_root = os.path.join(base, bundle.get_bundle_id()) - - instance_dir = os.path.join(activity_root, 'instance') - mkpath(instance_dir) - - data_dir = os.path.join(activity_root, 'data') - mkpath(data_dir) - - tmp_dir = os.path.join(activity_root, 'tmp') - mkpath(tmp_dir) - - os.environ['SUGAR_ACTIVITY_ROOT'] = activity_root - os.environ['SUGAR_BUNDLE_PATH'] = bundle.get_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()) - - # must be done early, some activities set translations globally, SL #3654 - activity_locale_path = os.environ.get("SUGAR_LOCALEDIR", - config.locale_path) - - gettext.bindtextdomain(bundle.get_bundle_id(), activity_locale_path) - gettext.bindtextdomain('sugar-toolkit-gtk3', config.locale_path) - gettext.textdomain(bundle.get_bundle_id()) - - splitted_module = activity_class.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) - - if not options.activity_id: - # Generate random hash - data = '%s%s' % (time.time(), random.randint(10000, 100000)) - random_hash = hashlib.sha1(data).hexdigest() - options.activity_id = random_hash - options.bundle_id = bundle.get_bundle_id() - - activity_handle = activityhandle.ActivityHandle( - activity_id=options.activity_id, - object_id=options.object_id, uri=options.uri, - invited=options.invited) - - 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: - try: - single_process = sessionbus.get_object(service_name, - service_path) - single_process.create( - activity_handle.get_dict(), - dbus_interface='org.laptop.SingleProcess') - - print 'Created %s in a single process.' % service_name - sys.exit(0) - except (TypeError, dbus.DBusException): - print 'Could not communicate with the instance process,' \ - 'launching a new process' - - if hasattr(module, 'start'): - module.start() - - instance = create_activity_instance(activity_constructor, activity_handle) - - if hasattr(instance, 'run_main_loop'): - instance.run_main_loop() - -main() +activityinstance.main() diff --git a/bin/sugar-activity-web b/bin/sugar-activity-web index b204b3e6..8fd7a59c 100644 --- a/bin/sugar-activity-web +++ b/bin/sugar-activity-web @@ -18,7 +18,7 @@ # Boston, MA 02111-1307, USA. if [ "$SUGAR_USE_WEBKIT1" = "yes" ]; then - exec sugar-activity sugar3.activity.webkit1.WebActivity $@ + exec sugar-activity3 sugar3.activity.webkit1.WebActivity $@ else - exec sugar-activity sugar3.activity.webactivity.WebActivity $@ + exec sugar-activity3 sugar3.activity.webactivity.WebActivity $@ fi diff --git a/bin/sugar-activity3 b/bin/sugar-activity3 new file mode 100755 index 00000000..6798bc32 --- /dev/null +++ b/bin/sugar-activity3 @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 + +from sugar3.activity import activityinstance + +activityinstance.main() diff --git a/configure.ac b/configure.ac index e0cfdc6d..e28e4b31 100644 --- a/configure.ac +++ b/configure.ac @@ -15,7 +15,7 @@ GNOME_COMPILE_WARNINGS(maximum) AC_PATH_PROG([GLIB_GENMARSHAL], [glib-genmarshal]) -PYTHON=python2 +PYTHON=python3 AM_PATH_PYTHON PKG_CHECK_MODULES(EXT, gtk+-3.0 gdk-3.0 gdk-pixbuf-2.0 sm ice alsa diff --git a/doc/conf.py b/doc/conf.py index 7faa8b40..7891c2d2 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -226,25 +226,25 @@ htmlhelp_basename = 'SugarToolkitGTK3doc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # Additional stuff for the LaTeX preamble. + #'preamble': '', -# Latex figure (float) alignment -#'figure_align': 'htbp', + # Latex figure (float) alignment + #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'SugarToolkitGTK3.tex', u'Sugar Toolkit GTK3 Documentation', - u'Sugar Labs', 'manual'), + (master_doc, 'SugarToolkitGTK3.tex', u'Sugar Toolkit GTK3 Documentation', + u'Sugar Labs', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -287,9 +287,9 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'SugarToolkitGTK3', u'Sugar Toolkit GTK3 Documentation', - author, 'SugarToolkitGTK3', 'One line description of project.', - 'Miscellaneous'), + (master_doc, 'SugarToolkitGTK3', u'Sugar Toolkit GTK3 Documentation', + author, 'SugarToolkitGTK3', 'One line description of project.', + 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. diff --git a/examples/alert.py b/examples/alert.py index f629a2ff..f0425cb5 100644 --- a/examples/alert.py +++ b/examples/alert.py @@ -21,11 +21,11 @@ box.pack_start(alert, False, False, 0) # Called when an alert object throws a response event. def __alert_response_cb(alert, response_id): if response_id is Gtk.ResponseType.OK: - print 'Continue Button was clicked.' + print('Continue Button was clicked.') elif response_id is Gtk.ResponseType.CANCEL: - print 'Cancel Button was clicked.' + print('Cancel Button was clicked.') elif response_id == -1: - print 'Timeout occurred' + print('Timeout occurred') alert.connect('response', __alert_response_cb) diff --git a/examples/animator.py b/examples/animator.py index dcdad0e5..c6ede6fa 100644 --- a/examples/animator.py +++ b/examples/animator.py @@ -23,7 +23,7 @@ class _Animation(animator.Animation): def __animation_completed_cb(anim): - print 'Animation completed' + print('Animation completed') w = Gtk.Window() diff --git a/examples/colorbutton.py b/examples/colorbutton.py index 2e4fa4b8..09db0683 100644 --- a/examples/colorbutton.py +++ b/examples/colorbutton.py @@ -24,7 +24,7 @@ separator.show() def color_changed_cb(button, pspec): - print button.get_color() + print(button.get_color()) color_button = ColorToolButton() diff --git a/examples/combobox.py b/examples/combobox.py index 61bf11c9..6d1c1a06 100644 --- a/examples/combobox.py +++ b/examples/combobox.py @@ -6,7 +6,7 @@ set_theme() def __combo_changed_cb(combo): - print 'Combo changed to %r' % combo.get_value() + print('Combo changed to %r' % combo.get_value()) w = Gtk.Window() diff --git a/examples/customdestroy.py b/examples/customdestroy.py index 723e28c6..fc33eb9e 100644 --- a/examples/customdestroy.py +++ b/examples/customdestroy.py @@ -13,14 +13,14 @@ class MyCellRenderer(Gtk.CellRenderer): Gtk.CellRenderer.__init__(self) def __del__(self): - print "cellrenderer destroy" + print("cellrenderer destroy") def do_render(self, cairo_t, widget, background_area, cell_area, flags): pass def window_destroy_cb(*kwargs): - print "window destroy" + print("window destroy") Gtk.main_quit() @@ -30,7 +30,7 @@ window.show() def treeview_destroy_cb(*kwargs): - print "treeview destroy" + print("treeview destroy") treeview = Gtk.TreeView() diff --git a/examples/gtktreesensitive.py b/examples/gtktreesensitive.py index 15b8856a..58fea997 100644 --- a/examples/gtktreesensitive.py +++ b/examples/gtktreesensitive.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 from gi.repository import Gtk import common diff --git a/examples/iconentry.py b/examples/iconentry.py index f5e4ea51..e35c6012 100644 --- a/examples/iconentry.py +++ b/examples/iconentry.py @@ -6,11 +6,11 @@ set_theme() def __go_next_cb(entry, icon_pos, data=None): - print 'Go next' + print('Go next') def __entry_activate_cb(widget, data=None): - print 'Entry activate' + print('Entry activate') w = Gtk.Window() diff --git a/examples/radiotoolbutton.py b/examples/radiotoolbutton.py index 08a92a47..5d0b5472 100644 --- a/examples/radiotoolbutton.py +++ b/examples/radiotoolbutton.py @@ -19,7 +19,7 @@ box.show() def echo(button, label): if not button.props.active: return - print label + print(label) palette = RadioPalette() diff --git a/examples/scrollingdetector.py b/examples/scrollingdetector.py index e3ba9f5b..df0c70a0 100644 --- a/examples/scrollingdetector.py +++ b/examples/scrollingdetector.py @@ -12,12 +12,12 @@ import common def _scroll_start_cb(event, treeview, invoker): - print "Scroll starts" + print("Scroll starts") invoker.detach() def _scroll_end_cb(event, treeview, invoker): - print "Scroll ends" + print("Scroll ends") invoker.attach_treeview(treeview) @@ -30,7 +30,7 @@ data_dir = os.getenv('GTK_DATA_PREFIX', '/usr/') iconlist = os.listdir(os.path.join(data_dir, 'share/icons/sugar/scalable/actions/')) -print "Displaying %s icons" % len(iconlist) +print("Displaying %s icons" % len(iconlist)) for icon in iconlist: icon = os.path.basename(icon) icon = icon[:icon.find('.')] diff --git a/examples/tabs.py b/examples/tabs.py index 47b1b45d..7a8b52fc 100644 --- a/examples/tabs.py +++ b/examples/tabs.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 from gi.repository import Gtk from sugar3.graphics.icon import Icon diff --git a/examples/toolbutton.py b/examples/toolbutton.py index aa4a85fe..c4a26491 100644 --- a/examples/toolbutton.py +++ b/examples/toolbutton.py @@ -21,7 +21,7 @@ toolbar_box.toolbar.insert(separator, -1) def __clicked_cb(button): n = int(button.get_tooltip()) button.set_tooltip(str(n + 1)) - print "tool button click count %d" % n + print("tool button click count %d" % n) tool_button = ToolButton(icon_name='view-radial', tooltip='0') diff --git a/src/sugar3/activity/Makefile.am b/src/sugar3/activity/Makefile.am index ace612e9..e3cf05bc 100644 --- a/src/sugar3/activity/Makefile.am +++ b/src/sugar3/activity/Makefile.am @@ -2,6 +2,7 @@ sugardir = $(pythondir)/sugar3/activity sugar_PYTHON = \ __init__.py \ activity.py \ + activityinstance.py \ activityfactory.py \ activityhandle.py \ activityservice.py \ diff --git a/src/sugar3/activity/activity.py b/src/sugar3/activity/activity.py index fdb7db01..0fbd1de4 100644 --- a/src/sugar3/activity/activity.py +++ b/src/sugar3/activity/activity.py @@ -158,6 +158,7 @@ Hint: A good and simple activity to learn from is the Read activity. You may copy it and use it as a template. ''' +import six import gettext import logging import os @@ -165,7 +166,6 @@ import signal import time from hashlib import sha1 from functools import partial -import StringIO import cairo import json @@ -206,7 +206,9 @@ from errno import EEXIST from gi.repository import SugarExt -_ = lambda msg: gettext.dgettext('sugar-toolkit-gtk3', msg) + +def _(msg): return gettext.dgettext('sugar-toolkit-gtk3', msg) + SCOPE_PRIVATE = 'private' SCOPE_INVITE_ONLY = 'invite' # shouldn't be shown in UI, it's implicit @@ -881,7 +883,7 @@ class Activity(Window, Gtk.Container): cr.set_source_surface(screenshot_surface) cr.paint() - preview_str = StringIO.StringIO() + preview_str = six.BytesIO() preview_surface.write_to_png(preview_str) return preview_str.getvalue() @@ -919,7 +921,7 @@ class Activity(Window, Gtk.Container): buddies_dict = self._get_buddies() if buddies_dict: - self.metadata['buddies_id'] = json.dumps(buddies_dict.keys()) + self.metadata['buddies_id'] = json.dumps(list(buddies_dict.keys())) self.metadata['buddies'] = json.dumps(self._get_buddies()) # update spent time before saving @@ -1103,8 +1105,9 @@ class Activity(Window, Gtk.Container): 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)) + logging.debug( + 'Requesting %s share of activity %s.' % + (verb, self._activity_id)) pservice = presenceservice.get_instance() pservice.connect('activity-shared', self.__share_cb) pservice.share_activity(self, private=private) @@ -1197,8 +1200,8 @@ class Activity(Window, Gtk.Container): if response_id == Gtk.ResponseType.OK: title = alert.entry.get_text() if self._is_resumed and \ - title == self._original_title: - datastore.delete(self._jobject_old.get_object_id()) + title == self._original_title: + datastore.delete(self._jobject_old.get_object_id()) self._jobject.metadata['title'] = title self._do_close(False) @@ -1222,7 +1225,7 @@ class Activity(Window, Gtk.Container): label = _('Save new') tip = _('Save a new journal entry') if self._is_resumed and \ - title == self._original_title: + title == self._original_title: label = _('Save') tip = _('Save into the old journal entry') @@ -1244,7 +1247,7 @@ class Activity(Window, Gtk.Container): if not skip_save: try: self.save() - except: + except BaseException: # pylint: disable=W0702 logging.exception('Error saving activity object to datastore') self._show_keep_failed_dialog() @@ -1306,12 +1309,12 @@ class Activity(Window, Gtk.Container): def __realize_cb(self, window): display_name = Gdk.Display.get_default().get_name() - if ':' in display_name: + if ':' in display_name: # X11 for sure; this only works in X11 xid = window.get_window().get_xid() SugarExt.wm_set_bundle_id(xid, self.get_bundle_id()) SugarExt.wm_set_activity_id(xid, str(self._activity_id)) - elif display_name is 'Broadway': + elif display_name is 'Broadway': # GTK3's HTML5 backend # This is needed so that the window takes the whole browser window self.maximize() @@ -1432,7 +1435,7 @@ class _ClientHandler(dbus.service.Object, DBusProperties): } filter_dict = dbus.Dictionary(filters, signature='sv') logging.debug('__get_filters_cb %r' % dbus.Array([filter_dict], - signature='a{sv}')) + signature='a{sv}')) return dbus.Array([filter_dict], signature='a{sv}') @dbus.service.method(dbus_interface=CLIENT_HANDLER, @@ -1448,9 +1451,10 @@ class _ClientHandler(dbus.service.Object, DBusProperties): handle_type = properties[CHANNEL + '.TargetHandleType'] if channel_type == CHANNEL_TYPE_TEXT: self._got_channel_cb(connection, object_path, handle_type) - except Exception, e: + except Exception as e: logging.exception(e) + _session = None @@ -1503,7 +1507,7 @@ def get_activity_root(): activity_root = env.get_profile_path(os.environ['SUGAR_BUNDLE_ID']) try: os.mkdir(activity_root) - except OSError, e: + except OSError as e: if e.errno != EEXIST: raise e return activity_root diff --git a/src/sugar3/activity/activityfactory.py b/src/sugar3/activity/activityfactory.py index 31895a5d..b46ecceb 100644 --- a/src/sugar3/activity/activityfactory.py +++ b/src/sugar3/activity/activityfactory.py @@ -23,7 +23,6 @@ the moment there is no reason to stabilize this API. """ import logging - import dbus from gi.repository import GObject from gi.repository import GLib @@ -53,7 +52,7 @@ except ValueError: def _close_fds(): - for i in xrange(3, MAXFD): + for i in range(3, MAXFD): try: os.close(i) # pylint: disable=W0704 @@ -69,7 +68,7 @@ def create_activity_id(): def _mkdir(path): try: os.mkdir(path) - except OSError, e: + except OSError as e: if e.errno != EEXIST: raise e @@ -137,10 +136,10 @@ def open_log_file(activity): 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_WRONLY, 0644) - f = os.fdopen(fd, 'w', 0) + fd = os.open(path, os.O_EXCL | os.O_CREAT | os.O_WRONLY, 0o644) + f = os.fdopen(fd, 'w') return (path, f) - except OSError, e: + except OSError as e: if e.errno == EEXIST: i += 1 elif e.errno == ENOSPC: @@ -225,7 +224,7 @@ class ActivityCreationHandler(GObject.GObject): self._handle.object_id, self._handle.uri, self._handle.invited) - dev_null = file('/dev/null', 'r') + dev_null = open('/dev/null', 'r') child = subprocess.Popen([str(s) for s in command], env=environ, cwd=str(self._bundle.get_path()), diff --git a/src/sugar3/activity/activityhandle.py b/src/sugar3/activity/activityhandle.py index d7973a99..d989a892 100644 --- a/src/sugar3/activity/activityhandle.py +++ b/src/sugar3/activity/activityhandle.py @@ -24,26 +24,26 @@ journal object id's, class ActivityHandle(object): ''' Data structure storing simple activity metadata - + Args: activity_id (string): unique id for the activity to be created - + object_id (string): identity of the journal object associated with the activity. - + When you resume an activity from the journal the object_id will be passed in. It is optional since new activities does not have an associated object. - + uri (string): 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) - - invited (bool): True if the activity is being + + invited (bool): True if the activity is being launched for handling an invite from the network ''' @@ -55,7 +55,7 @@ class ActivityHandle(object): self.invited = invited def get_dict(self): - '''Returns activity settings as a dictionary in format + '''Returns activity settings as a dictionary in format {activity_id:XXXX, object_id:XXXX, uri:XXXX, invited:BOOL}''' result = {'activity_id': self.activity_id, 'invited': self.invited} if self.object_id: diff --git a/src/sugar3/activity/activityinstance.py b/src/sugar3/activity/activityinstance.py new file mode 100644 index 00000000..b756f54f --- /dev/null +++ b/src/sugar3/activity/activityinstance.py @@ -0,0 +1,222 @@ +# Copyright (C) 2006-2008, Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import sys +import six +import logging + +# Change the default encoding to avoid UnicodeDecodeError +# http://lists.sugarlabs.org/archive/sugar-devel/2012-August/038928.html +if six.PY2: + reload(sys) + sys.setdefaultencoding('utf-8') + +import gettext +from optparse import OptionParser + +import dbus +import dbus.service +from dbus.mainloop.glib import DBusGMainLoop +DBusGMainLoop(set_as_default=True) + +from sugar3.activity import activityhandle +from sugar3 import config +from sugar3.bundle.activitybundle import ActivityBundle +from sugar3 import logger + +from sugar3.bundle.bundle import MalformedBundleException + +from distutils.dir_util import mkpath +import time +import hashlib +import random + + +def create_activity_instance(constructor, handle): + activity = constructor(handle) + activity.show() + return activity + + +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{sv}') + def create(self, handle_dict): + handle = activityhandle.create_from_dict(handle_dict) + create_activity_instance(self.constructor, handle) + + +def main(): + usage = 'usage: %prog [options] [activity dir] [python class]' + epilog = 'If you are running from a directory containing an Activity, ' \ + 'the argument may be omitted. Otherwise please provide either '\ + 'a directory containing a Sugar Activity [activity dir], a '\ + '[python_class], or both.' + + parser = OptionParser(usage=usage, epilog=epilog) + 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') + parser.add_option('-i', '--invited', dest='invited', + action='store_true', default=False, + help='the activity is being launched for handling an ' + 'invite from the network') + (options, args) = parser.parse_args() + + logger.start() + + activity_class = None + if len(args) == 2: + activity_class = args[1] + os.chdir(args[0]) + elif len(args) == 1: + if os.path.isdir(args[0]): + os.chdir(args[0]) + else: + activity_class = args[0] + + os.environ['SUGAR_BUNDLE_PATH'] = os.path.abspath(os.curdir) + + bundle_path = os.environ['SUGAR_BUNDLE_PATH'] + sys.path.insert(0, bundle_path) + + try: + bundle = ActivityBundle(bundle_path) + except MalformedBundleException: + parser.print_help() + exit(0) + + if not activity_class: + command = bundle.get_command() + if command.startswith('sugar-activity'): + if not command.startswith('sugar-activity3'): + logging.warning("Activity written for Python 2, consider porting to Python 3.") + activity_class = command.split(" ")[1] + + if 'SUGAR_VERSION' not in os.environ: + profile_id = os.environ.get('SUGAR_PROFILE', 'default') + home_dir = os.environ.get('SUGAR_HOME', os.path.expanduser('~/.sugar')) + base = os.path.join(home_dir, profile_id) + activity_root = os.path.join(base, bundle.get_bundle_id()) + + instance_dir = os.path.join(activity_root, 'instance') + mkpath(instance_dir) + + data_dir = os.path.join(activity_root, 'data') + mkpath(data_dir) + + tmp_dir = os.path.join(activity_root, 'tmp') + mkpath(tmp_dir) + + os.environ['SUGAR_ACTIVITY_ROOT'] = activity_root + os.environ['SUGAR_BUNDLE_PATH'] = bundle.get_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()) + + # must be done early, some activities set translations globally, SL #3654 + activity_locale_path = os.environ.get("SUGAR_LOCALEDIR", + config.locale_path) + + gettext.bindtextdomain(bundle.get_bundle_id(), activity_locale_path) + gettext.bindtextdomain('sugar-toolkit-gtk3', config.locale_path) + gettext.textdomain(bundle.get_bundle_id()) + + splitted_module = activity_class.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) + + if not options.activity_id: + # Generate random hash + data = '%s%s' % (time.time(), random.randint(10000, 100000)) + random_hash = hashlib.sha1(data.encode()).hexdigest() + options.activity_id = random_hash + options.bundle_id = bundle.get_bundle_id() + + activity_handle = activityhandle.ActivityHandle( + activity_id=options.activity_id, + object_id=options.object_id, uri=options.uri, + invited=options.invited) + + 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: + try: + single_process = sessionbus.get_object(service_name, + service_path) + single_process.create( + activity_handle.get_dict(), + dbus_interface='org.laptop.SingleProcess') + + print('Created %s in a single process.' % service_name) + sys.exit(0) + except (TypeError, dbus.DBusException): + print('Could not communicate with the instance process,' + 'launching a new process') + + if hasattr(module, 'start'): + module.start() + + instance = create_activity_instance(activity_constructor, activity_handle) + + if hasattr(instance, 'run_main_loop'): + instance.run_main_loop() diff --git a/src/sugar3/activity/activityservice.py b/src/sugar3/activity/activityservice.py index 738e602e..25329029 100644 --- a/src/sugar3/activity/activityservice.py +++ b/src/sugar3/activity/activityservice.py @@ -79,5 +79,5 @@ class ActivityService(dbus.service.Object): def GetDocumentPath(self, async_cb, async_err_cb): try: self._activity.get_document_path(async_cb, async_err_cb) - except Exception, e: + except Exception as e: async_err_cb(e) diff --git a/src/sugar3/activity/bundlebuilder.py b/src/sugar3/activity/bundlebuilder.py index 40348445..f270346c 100644 --- a/src/sugar3/activity/bundlebuilder.py +++ b/src/sugar3/activity/bundlebuilder.py @@ -40,12 +40,13 @@ import gettext import logging from glob import glob from fnmatch import fnmatch -from ConfigParser import ConfigParser +from six.moves.configparser import ConfigParser import xml.etree.cElementTree as ET -from HTMLParser import HTMLParser +from six.moves.html_parser import HTMLParser from sugar3 import env from sugar3.bundle.activitybundle import ActivityBundle +from six.moves import reduce IGNORE_DIRS = ['dist', '.git', 'screenshots'] @@ -150,7 +151,7 @@ class Builder(object): args = ['msgfmt', '--output-file=%s' % mo_file, file_name] retcode = subprocess.call(args) if retcode: - print 'ERROR - msgfmt failed with return code %i.' % retcode + print('ERROR - msgfmt failed with return code %i.' % retcode) if self._no_fail: continue @@ -301,8 +302,8 @@ class Installer(Packager): source_to_dest[source_path] = dest_path - for source, dest in source_to_dest.items(): - print 'Install %s to %s.' % (source, dest) + for source, dest in list(source_to_dest.items()): + print('Install %s to %s.' % (source, dest)) path = os.path.dirname(dest) if not os.path.exists(path): @@ -429,7 +430,7 @@ def cmd_check(config, options): if options.choice == 'integration': run_unit_test = False - print "Running Tests" + print("Running Tests") test_path = os.path.join(config.source_dir, "tests") @@ -443,22 +444,22 @@ def cmd_check(config, options): all_tests = unittest.defaultTestLoader.discover(unit_test_path) unittest.TextTestRunner(verbosity=options.verbose).run(all_tests) elif not run_unit_test: - print "Not running unit tests" + print("Not running unit tests") else: - print 'No "unit" directory found.' + print('No "unit" directory found.') if os.path.isdir(integration_test_path) and run_integration_test: all_tests = unittest.defaultTestLoader.discover( integration_test_path) unittest.TextTestRunner(verbosity=options.verbose).run(all_tests) elif not run_integration_test: - print "Not running integration tests" + print("Not running integration tests") else: - print 'No "integration" directory found.' + print('No "integration" directory found.') - print "Finished testing" + print("Finished testing") else: - print "Error: No tests/ directory" + print("Error: No tests/ directory") def cmd_dev(config, options): @@ -472,9 +473,9 @@ def cmd_dev(config, options): 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.' + print('ERROR - The bundle has been already setup for development.') else: - print 'ERROR - A bundle with the same name is already installed.' + print('ERROR - A bundle with the same name is already installed.') def cmd_dist_xo(config, options): @@ -490,9 +491,9 @@ def cmd_dist_xo(config, options): def cmd_fix_manifest(config, options): '''Add missing files to the manifest (OBSOLETE)''' - print 'WARNING: The fix_manifest command is obsolete.' - print ' The MANIFEST file is no longer used in bundles,' - print ' please remove it.' + print('WARNING: The fix_manifest command is obsolete.') + print(' The MANIFEST file is no longer used in bundles,') + print(' please remove it.') def cmd_dist_source(config, options): @@ -506,7 +507,10 @@ def cmd_install(config, options): """Install the activity in the system""" installer = Installer(Builder(config)) - installer.install(options.prefix, options.install_mime, options.install_desktop_file) + installer.install( + options.prefix, + options.install_mime, + options.install_desktop_file) def _po_escape(string): @@ -568,7 +572,7 @@ def cmd_genpot(config, options): args += python_files retcode = subprocess.call(args) if retcode: - print 'ERROR - xgettext failed with return code %i.' % retcode + print('ERROR - xgettext failed with return code %i.' % retcode) def cmd_build(config, options): @@ -603,7 +607,7 @@ def start(): choices=['unit', 'integration'], help="run unit/integration test") check_parser.add_argument("--verbosity", "-v", dest="verbose", - type=int, choices=range(0, 3), + type=int, choices=list(range(0, 3)), default=1, nargs='?', help="verbosity for the unit tests") diff --git a/src/sugar3/activity/webkit1.py b/src/sugar3/activity/webkit1.py index a2d01548..8ea7dade 100644 --- a/src/sugar3/activity/webkit1.py +++ b/src/sugar3/activity/webkit1.py @@ -28,8 +28,8 @@ GObject.threads_init() from gi.repository import WebKit import socket from threading import Thread -from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -import SocketServer +from six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +from six.moves import socketserver import select import errno import mimetypes @@ -80,7 +80,7 @@ class LocalHTTPServer(HTTPServer): # shutdown request and wastes cpu at all other times. try: r, w, e = select.select([self], [], [], poll_interval) - except select.error, e: + except select.error as e: if e[0] == errno.EINTR: logging.debug("got eintr") continue @@ -92,7 +92,7 @@ class LocalHTTPServer(HTTPServer): def server_bind(self): """Override server_bind in HTTPServer to not use getfqdn to get the server name because is very slow.""" - SocketServer.TCPServer.server_bind(self) + socketserver.TCPServer.server_bind(self) _host, port = self.socket.getsockname()[:2] self.server_name = 'localhost' self.server_port = port diff --git a/src/sugar3/activity/widgets.py b/src/sugar3/activity/widgets.py index 06606e80..21e71728 100644 --- a/src/sugar3/activity/widgets.py +++ b/src/sugar3/activity/widgets.py @@ -34,7 +34,7 @@ from sugar3.graphics.palettemenu import PaletteMenuBox from sugar3 import profile -_ = lambda msg: gettext.dgettext('sugar-toolkit-gtk3', msg) +def _(msg): return gettext.dgettext('sugar-toolkit-gtk3', msg) def _create_activity_icon(metadata): diff --git a/src/sugar3/bundle/__init__.py b/src/sugar3/bundle/__init__.py index 9b8f78f7..ea06fb45 100644 --- a/src/sugar3/bundle/__init__.py +++ b/src/sugar3/bundle/__init__.py @@ -44,7 +44,9 @@ an `[Activity]` header on the first line: * `icon` - the icon file for the activity, shown by Sugar in the list of installed activities, -* `exec` - how to execute the activity, e.g. `sugar-activity module.Class`, +* `exec` - how to execute the activity, e.g. + `sugar-activity3 module.Class` (For activities written for Python 3), + `sugar-activity module.Class` (For activities written for Python 2) Optional metadata keys are; @@ -117,7 +119,7 @@ Example `activity.info` [Activity] name = Browse bundle_id = org.laptop.WebActivity - exec = sugar-activity webactivity.WebActivity -s + exec = sugar-activity3 webactivity.WebActivity -s activity_version = 200 icon = activity-web max_participants = 100 diff --git a/src/sugar3/bundle/activitybundle.py b/src/sugar3/bundle/activitybundle.py index c0d9d985..879831e5 100644 --- a/src/sugar3/bundle/activitybundle.py +++ b/src/sugar3/bundle/activitybundle.py @@ -20,7 +20,7 @@ UNSTABLE. """ -from ConfigParser import ConfigParser, ParsingError +from six.moves.configparser import ConfigParser, ParsingError from locale import normalize import os import shutil @@ -441,7 +441,7 @@ class ActivityBundle(Bundle): if delete_profile: bundle_profile_path = env.get_profile_path(self._bundle_id) if os.path.exists(bundle_profile_path): - os.chmod(bundle_profile_path, 0775) + os.chmod(bundle_profile_path, 0o775) shutil.rmtree(bundle_profile_path, ignore_errors=True) self._uninstall(install_path) diff --git a/src/sugar3/bundle/bundle.py b/src/sugar3/bundle/bundle.py index 59cd885a..c5716107 100644 --- a/src/sugar3/bundle/bundle.py +++ b/src/sugar3/bundle/bundle.py @@ -20,10 +20,10 @@ UNSTABLE. """ +import six import os import logging import shutil -import StringIO import zipfile @@ -74,7 +74,7 @@ class Bundle(object): if not os.path.isdir(self._path): try: self._zip_file = zipfile.ZipFile(self._path) - except zipfile.error, exception: + except zipfile.error as exception: raise MalformedBundleException('Error accessing zip file %r: ' '%s' % (self._path, exception)) self._check_zip_bundle() @@ -115,7 +115,7 @@ class Bundle(object): if self._zip_file is None: path = os.path.join(self._path, filename) try: - f = open(path, 'rb') + f = open(path, 'r') except IOError: logging.debug("cannot open path %s" % path) return None @@ -123,7 +123,7 @@ class Bundle(object): path = os.path.join(self._zip_root_dir, filename) try: data = self._zip_file.read(path) - f = StringIO.StringIO(data) + f = six.StringIO(data) except KeyError: logging.debug('%s not found in zip %s.' % (filename, path)) return None @@ -171,7 +171,7 @@ class Bundle(object): raise AlreadyInstalledException if not os.path.isdir(install_dir): - os.mkdir(install_dir, 0775) + os.mkdir(install_dir, 0o775) # zipfile provides API that in theory would let us do this # correctly by hand, but handling all the oddities of diff --git a/src/sugar3/bundle/bundleversion.py b/src/sugar3/bundle/bundleversion.py index 91df406a..7ad9e3e0 100644 --- a/src/sugar3/bundle/bundleversion.py +++ b/src/sugar3/bundle/bundleversion.py @@ -82,6 +82,7 @@ class NormalizedVersion(object): Attributes: parts (list): the numeric parts of the version after normalization. """ + def __init__(self, activity_version): self._activity_version = activity_version self.parts = [] diff --git a/src/sugar3/bundle/contentbundle.py b/src/sugar3/bundle/contentbundle.py index 146e7f8c..0e61ade0 100644 --- a/src/sugar3/bundle/contentbundle.py +++ b/src/sugar3/bundle/contentbundle.py @@ -21,10 +21,11 @@ UNSTABLE. """ -from ConfigParser import ConfigParser +from six.moves import urllib +from six.moves.configparser import ConfigParser + import tempfile import os -import urllib from sugar3 import env from sugar3.bundle.bundle import Bundle, MalformedBundleException @@ -142,7 +143,7 @@ class ContentBundle(Bundle): def get_start_uri(self): path = os.path.join(self.get_path(), self._activity_start) - return 'file://' + urllib.pathname2url(path) + return 'file://' + urllib.request.pathname2url(path) def get_bundle_id(self): return self._global_name diff --git a/src/sugar3/datastore/datastore.py b/src/sugar3/datastore/datastore.py index f132f0c8..e1b3f8b9 100644 --- a/src/sugar3/datastore/datastore.py +++ b/src/sugar3/datastore/datastore.py @@ -20,6 +20,7 @@ STABLE """ +import six import logging import time from datetime import datetime @@ -69,6 +70,7 @@ def __datastore_updated_cb(object_id): def __datastore_deleted_cb(object_id): deleted.send(None, object_id=object_id) + created = dispatch.Signal() deleted = dispatch.Signal() updated = dispatch.Signal() @@ -85,6 +87,12 @@ class DSMetadata(GObject.GObject): if not properties: self._properties = {} else: + if six.PY3: + for x, y in properties.items(): + try: + properties[x] = y.decode() + except BaseException: + pass self._properties = properties default_keys = ['activity', 'activity_id', @@ -97,6 +105,11 @@ class DSMetadata(GObject.GObject): return self._properties[key] def __setitem__(self, key, value): + if six.PY3: + try: + value = value.decode() + except BaseException: + pass if key not in self._properties or self._properties[key] != value: self._properties[key] = value self.emit('updated') @@ -112,7 +125,7 @@ class DSMetadata(GObject.GObject): return key in self._properties def keys(self): - return self._properties.keys() + return list(self._properties.keys()) def get_dictionary(self): return self._properties @@ -128,7 +141,7 @@ class DSMetadata(GObject.GObject): def update(self, properties): """Update all of the metadata""" - for (key, value) in properties.items(): + for (key, value) in list(properties.items()): self[key] = value diff --git a/src/sugar3/dispatch/dispatcher.py b/src/sugar3/dispatch/dispatcher.py index 89d219c1..4b437a3a 100644 --- a/src/sugar3/dispatch/dispatcher.py +++ b/src/sugar3/dispatch/dispatcher.py @@ -1,4 +1,6 @@ import weakref +import six + try: set except NameError: @@ -11,7 +13,7 @@ WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref) def _make_id(target): if hasattr(target, 'im_func'): - return (id(target.im_self), id(target.im_func)) + return (id(im_self(target)), id(im_func(target))) return id(target) @@ -159,7 +161,7 @@ class Signal(object): for receiver in self._live_receivers(_make_id(sender)): try: response = receiver(signal=self, sender=sender, **named) - except Exception, err: + except Exception as err: responses.append((receiver, err)) else: responses.append((receiver, response)) @@ -195,3 +197,17 @@ class Signal(object): for idx, (r_key, _) in enumerate(self.receivers): if r_key == key: del self.receivers[idx] + + +def im_self(func): + if six.PY2: + return func.im_self + elif six.PY3: + return func.__self__ + + +def im_func(func): + if six.PY2: + return func.im_func + elif six.PY3: + return func.__func__ diff --git a/src/sugar3/dispatch/saferef.py b/src/sugar3/dispatch/saferef.py index df899cdd..581703c2 100644 --- a/src/sugar3/dispatch/saferef.py +++ b/src/sugar3/dispatch/saferef.py @@ -5,6 +5,7 @@ Provides a way to safely weakref any function, including bound methods (which aren't handled by the core weakref module). """ +import six import weakref import traceback @@ -21,7 +22,7 @@ def safeRef(target, onDelete=None): weakref or a BoundMethodWeakref) as argument. """ if hasattr(target, 'im_self'): - if target.im_self is not None: + if im_self(target) is not None: # Turn a bound method into a BoundMethodWeakref instance. # Keep track of these instances for lookup by disconnect(). assert hasattr(target, 'im_func'), \ @@ -123,18 +124,18 @@ class BoundMethodWeakref(object): try: if callable(function): function(self) - except Exception, e: + except Exception as e: try: traceback.print_exc() except AttributeError: - print "Exception during saferef %s cleanup " - "function %s: %s" % (self, function, e) + print("Exception during saferef %s cleanup " + "function %s: %s" % (self, function, e)) self.deletionMethods = [onDelete] self.key = self.calculateKey(target) - self.weakSelf = weakref.ref(target.im_self, remove) - self.weakFunc = weakref.ref(target.im_func, remove) - self.selfName = str(target.im_self) - self.funcName = str(target.im_func.__name__) + self.weakSelf = weakref.ref(im_self(target), remove) + self.weakFunc = weakref.ref(im_func(target), remove) + self.selfName = str(im_self(target)) + self.funcName = str(im_func(target).__name__) def calculateKey(cls, target): """Calculate the reference key for this reference @@ -142,7 +143,7 @@ class BoundMethodWeakref(object): Currently this is a two-tuple of the id()'s of the target object and the target function respectively. """ - return (id(target.im_self), id(target.im_func)) + return (id(im_self(target)), id(im_func(target))) calculateKey = classmethod(calculateKey) def __str__(self): @@ -155,15 +156,19 @@ class BoundMethodWeakref(object): __repr__ = __str__ - def __nonzero__(self): + def __bool__(self): """Whether we are still a valid reference""" return self() is not None + def __nonzero__(self): + """Python2 alternative for __bool__""" + return self() is not None + def __cmp__(self, other): """Compare with another reference""" if not isinstance(other, self.__class__): - return cmp(self.__class__, type(other)) - return cmp(self.key, other.key) + return ((self.__class__ > type(other)) - (self.__class__ < type(other))) + return ((self.key > other.key) - (self.key < other.key)) def __call__(self): """Return a strong reference to the bound method @@ -201,6 +206,7 @@ class BoundNonDescriptorMethodWeakref(BoundMethodWeakref): aren't descriptors (such as Jython) this implementation has the advantage of working in the most cases. """ + def __init__(self, target, onDelete=None): """Return a weak-reference-like instance for a bound method @@ -215,9 +221,9 @@ class BoundNonDescriptorMethodWeakref(BoundMethodWeakref): collected). Should take a single argument, which will be passed a pointer to this object. """ - assert getattr(target.im_self, target.__name__) == target, \ + assert getattr(im_self(target), target.__name__) == target, \ ("method %s isn't available as the attribute %s of %s" % - (target, target.__name__, target.im_self)) + (target, target.__name__, im_self(target))) super(BoundNonDescriptorMethodWeakref, self).__init__(target, onDelete) def __call__(self): @@ -255,3 +261,17 @@ def get_bound_method_weakref(target, onDelete): # no luck, use the alternative implementation: return BoundNonDescriptorMethodWeakref(target=target, onDelete=onDelete) + + +def im_self(func): + if six.PY2: + return func.im_self + elif six.PY3: + return func.__self__ + + +def im_func(func): + if six.PY2: + return func.im_func + elif six.PY3: + return func.__func__ diff --git a/src/sugar3/env.py b/src/sugar3/env.py index a18d5c93..bbb1a9a1 100644 --- a/src/sugar3/env.py +++ b/src/sugar3/env.py @@ -36,9 +36,9 @@ def get_profile_path(path=None): base = os.path.join(home_dir, profile_id) if not os.path.isdir(base): try: - os.makedirs(base, 0770) + os.makedirs(base, 0o770) except OSError: - print 'Could not create user directory.' + print('Could not create user directory.') if path is not None: return os.path.join(base, path) diff --git a/src/sugar3/graphics/Makefile.am b/src/sugar3/graphics/Makefile.am index e5fdf09a..0621750e 100644 --- a/src/sugar3/graphics/Makefile.am +++ b/src/sugar3/graphics/Makefile.am @@ -16,7 +16,6 @@ sugar_PYTHON = \ palettemenu.py \ palettewindow.py \ panel.py \ - popwindow.py \ radiopalette.py \ radiotoolbutton.py \ scrollingdetector.py \ diff --git a/src/sugar3/graphics/alert.py b/src/sugar3/graphics/alert.py index d262a209..66cefd9d 100644 --- a/src/sugar3/graphics/alert.py +++ b/src/sugar3/graphics/alert.py @@ -61,7 +61,7 @@ from sugar3.graphics import style from sugar3.graphics.icon import Icon -_ = lambda msg: gettext.dgettext('sugar-toolkit-gtk3', msg) +def _(msg): return gettext.dgettext('sugar-toolkit-gtk3', msg) if not hasattr(GObject.ParamFlags, 'READWRITE'): @@ -258,6 +258,8 @@ class Alert(Gtk.EventBox): def __button_clicked_cb(self, button, response_id): self._response(response_id) + + if hasattr(Alert, 'set_css_name'): Alert.set_css_name('alert') @@ -294,9 +296,9 @@ class ConfirmationAlert(Alert): # Check the response identifier. if response_id is Gtk.ResponseType.OK: - print 'Ok Button was clicked.' + print('Ok Button was clicked.') elif response_id is Gtk.ResponseType.CANCEL: - print 'Cancel Button was clicked.' + print('Cancel Button was clicked.') """ def __init__(self, **kwargs): @@ -341,7 +343,7 @@ class ErrorAlert(Alert): # Check the response identifier. if response_id is Gtk.ResponseType.OK: - print 'Ok Button was clicked.' + print('Ok Button was clicked.') """ def __init__(self, **kwargs): @@ -390,6 +392,8 @@ class _TimeoutIcon(Gtk.Alignment): def set_text(self, text): self._text.set_markup('%s' % GLib.markup_escape_text(str(text))) + + if hasattr(_TimeoutIcon, 'set_css_name'): _TimeoutIcon.set_css_name('timeouticon') @@ -458,11 +462,11 @@ class TimeoutAlert(_TimeoutAlert): # Check the response identifier. if response_id is Gtk.ResponseType.OK: - print 'Continue Button was clicked.' + print('Continue Button was clicked.') elif response_id is Gtk.ResponseType.CANCEL: - print 'Cancel Button was clicked.' + print('Cancel Button was clicked.') elif response_id == -1: - print 'Timeout occurred' + print('Timeout occurred') """ def __init__(self, timeout=5, **kwargs): @@ -508,9 +512,9 @@ class NotifyAlert(_TimeoutAlert): # Check the response identifier. if response_id is Gtk.ResponseType.OK: - print 'Ok Button was clicked.' + print('Ok Button was clicked.') elif response_id == -1: - print 'Timeout occurred' + print('Timeout occurred') """ def __init__(self, timeout=5, **kwargs): diff --git a/src/sugar3/graphics/colorbutton.py b/src/sugar3/graphics/colorbutton.py index 2ca6851c..10c188ef 100644 --- a/src/sugar3/graphics/colorbutton.py +++ b/src/sugar3/graphics/colorbutton.py @@ -29,7 +29,7 @@ from sugar3.graphics.icon import Icon from sugar3.graphics.palette import Palette, ToolInvoker, WidgetInvoker -_ = lambda msg: gettext.dgettext('sugar-toolkit-gtk3', msg) +def _(msg): return gettext.dgettext('sugar-toolkit-gtk3', msg) if not hasattr(GObject.ParamFlags, 'READWRITE'): @@ -38,8 +38,8 @@ if not hasattr(GObject.ParamFlags, 'READWRITE'): def get_svg_color_string(color): - return '#%.2X%.2X%.2X' % (color.red / 257, color.green / 257, - color.blue / 257) + return '#%.2X%.2X%.2X' % (color.red // 257, color.green // 257, + color.blue // 257) class _ColorButton(Gtk.Button): @@ -123,8 +123,8 @@ class _ColorButton(Gtk.Button): context = self.get_style_context() fg_color = context.get_color(Gtk.StateType.NORMAL) # the color components are stored as float values between 0.0 and 1.0 - return '#%.2X%.2X%.2X' % (fg_color.red * 255, fg_color.green * 255, - fg_color.blue * 255) + return '#%.2X%.2X%.2X' % (int(fg_color.red * 255), int(fg_color.green * 255), + int(fg_color.blue * 255)) def set_color(self, color): assert isinstance(color, Gdk.Color) @@ -149,11 +149,11 @@ class _ColorButton(Gtk.Button): ''' Sets the icon for the tool button from a named themed icon. If it is none then no icon will be shown. - + Args: icon_name(string): The name for a themed icon. It can be set as 'None' too. - + Example: set_icon_name('view-radial') ''' @@ -169,11 +169,11 @@ class _ColorButton(Gtk.Button): icon_name = GObject.Property(type=str, getter=get_icon_name, setter=set_icon_name) - def set_icon_size(self, icon_size): - self._preview.props.icon_size = icon_size + def set_icon_size(self, pixel_size): + self._preview.props.pixel_size = pixel_size def get_icon_size(self): - return self._preview.props.icon_size + return self._preview.props.pixel_size icon_size = GObject.Property(type=int, getter=get_icon_size, setter=set_icon_size) @@ -496,13 +496,13 @@ class ColorToolButton(Gtk.ToolItem): def set_accelerator(self, accelerator): ''' Sets keyboard shortcut that activates this button. - + Args: accelerator(string): accelerator to be set. Should be in form Letter Find about format here : https://developer.gnome.org/gtk3/stable/gtk3-Keyboard-Accelerators.html#gtk-accelerator-parse - + Example: set_accelerator(self, 'accel') ''' @@ -523,21 +523,21 @@ class ColorToolButton(Gtk.ToolItem): The create_palette function is called when the palette needs to be invoked. For example, when the user has right clicked the icon or the user has hovered over the icon for a long time. - + The create_palette will only be called once or zero times. The palette returned will be stored and re-used if the user invokes the palette multiple times. - + Your create_palette implementation does not need to :any:`Gtk.Widget.show` the palette, as this will be done by the invoker. However, you still need to show the menu items, etc that you place in the palette. - + Returns: - + sugar3.graphics.palette.Palette, or None to indicate that you do not want a palette shown - + The default implementation returns None, to indicate no palette should be shown. ''' @@ -595,11 +595,11 @@ class ColorToolButton(Gtk.ToolItem): ''' Sets the icon for the tool button from a named themed icon. If it is none then no icon will be shown. - + Args: icon_name(string): The name for a themed icon. It can be set as 'None' too. - + Example: set_icon_name('view-radial') ''' @@ -632,8 +632,8 @@ class ColorToolButton(Gtk.ToolItem): def set_title(self, title): ''' - The set_title() method sets the "title" property to the value of - title. The "title" property contains the string that is used to + The set_title() method sets the "title" property to the value of + title. The "title" property contains the string that is used to set the colorbutton title. ''' self.get_child().props.title = title diff --git a/src/sugar3/graphics/icon.py b/src/sugar3/graphics/icon.py index 0666dc15..2343aee3 100644 --- a/src/sugar3/graphics/icon.py +++ b/src/sugar3/graphics/icon.py @@ -87,11 +87,13 @@ In this example, the badge will be centered at 97.0% on the X axis, and 85.0% on the Y axis. ''' +import six import re import math import logging import os -from ConfigParser import ConfigParser + +from six.moves.configparser import ConfigParser import gi gi.require_version('Rsvg', '2.0') @@ -127,8 +129,8 @@ class _SVGLoader(object): if cache: self._cache[file_name] = icon - for entity, value in entities.items(): - if isinstance(value, basestring): + for entity, value in list(entities.items()): + if isinstance(value, six.string_types): xml = '' % (entity, value) icon = re.sub('' % entity, xml, icon) else: @@ -207,7 +209,7 @@ class _IconBuffer(object): # try read from the .icon file icon_filename = info.get_filename().replace('.svg', '.icon') if icon_filename != info.get_filename() and \ - os.path.exists(icon_filename): + os.path.exists(icon_filename): try: with open(icon_filename) as config_file: @@ -470,7 +472,6 @@ class Icon(Gtk.Image): __gtype_name__ = 'SugarIcon' - # FIXME: deprecate icon_size _MENU_SIZES = (Gtk.IconSize.MENU, Gtk.IconSize.DND, Gtk.IconSize.SMALL_TOOLBAR, Gtk.IconSize.BUTTON) @@ -483,7 +484,6 @@ class Icon(Gtk.Image): self._alpha = 1.0 self._scale = 1.0 - # FIXME: deprecate icon_size if 'icon_size' in kwargs: logging.warning("icon_size is deprecated. Use pixel_size instead.") @@ -532,7 +532,6 @@ class Icon(Gtk.Image): if self._buffer.file_name != self.props.file: self._buffer.file_name = self.props.file - # FIXME: deprecate icon_size pixel_size = None if self.props.pixel_size == -1: if self.props.icon_size in self._MENU_SIZES: @@ -549,7 +548,7 @@ class Icon(Gtk.Image): self._buffer.height = height def _icon_size_changed_cb(self, image, pspec): - self._buffer.icon_size = self.props.icon_size + self._buffer.icon_size = self.props.pixel_size def _icon_name_changed_cb(self, image, pspec): self._buffer.icon_name = self.props.icon_name @@ -805,7 +804,7 @@ class EventIcon(Gtk.EventBox): # for example, after a touch palette invocation self.connect_after('button-release-event', self.__button_release_event_cb) - for key, value in kwargs.iteritems(): + for key, value in six.iteritems(kwargs): self.set_property(key, value) from sugar3.graphics.palette import CursorInvoker @@ -1152,6 +1151,8 @@ class CanvasIcon(EventIcon): def __palette_popdown_cb(self, palette): self.unset_state_flags(Gtk.StateFlags.PRELIGHT) + + if hasattr(CanvasIcon, 'set_css_name'): CanvasIcon.set_css_name('canvasicon') @@ -1447,6 +1448,6 @@ def get_surface(**kwargs): cairo surface or None if image was not found ''' icon = _IconBuffer() - for key, value in kwargs.items(): + for key, value in list(kwargs.items()): icon.__setattr__(key, value) return icon.get_surface() diff --git a/src/sugar3/graphics/iconentry.py b/src/sugar3/graphics/iconentry.py index 243807fa..bc474616 100644 --- a/src/sugar3/graphics/iconentry.py +++ b/src/sugar3/graphics/iconentry.py @@ -61,7 +61,7 @@ class IconEntry(Gtk.Entry): self.set_icon(position, pixbuf) def set_icon(self, position, pixbuf): - if type(pixbuf) is not GdkPixbuf.Pixbuf: + if not isinstance(pixbuf, GdkPixbuf.Pixbuf): raise ValueError('Argument must be a pixbuf, not %r.' % pixbuf) self.set_icon_from_pixbuf(position, pixbuf) diff --git a/src/sugar3/graphics/objectchooser.py b/src/sugar3/graphics/objectchooser.py index e7fad885..7cbe860f 100644 --- a/src/sugar3/graphics/objectchooser.py +++ b/src/sugar3/graphics/objectchooser.py @@ -19,8 +19,8 @@ STABLE. """ +import six import logging -import StringIO import cairo from gi.repository import GObject @@ -59,13 +59,13 @@ def get_preview_pixbuf(preview_data, width=-1, height=-1): None, if it could not be created Example: - pixbuf = get_preview_pixbuf(metadata.get('preview', '')) - has_preview = pixbuf is not None - - if has_preview: - im = Gtk.Image() - im.set_from_pixbuf(pixbuf) - box.add(im) + pixbuf = get_preview_pixbuf(metadata.get('preview', '')) + has_preview = pixbuf is not None + + if has_preview: + im = Gtk.Image() + im.set_from_pixbuf(pixbuf) + box.add(im) im.show() """ if width == -1: @@ -82,7 +82,7 @@ def get_preview_pixbuf(preview_data, width=-1, height=-1): import base64 preview_data = base64.b64decode(preview_data) - png_file = StringIO.StringIO(preview_data) + png_file = six.StringIO(preview_data) try: # Load image and scale to dimensions surface = cairo.ImageSurface.create_from_png(png_file) @@ -126,7 +126,7 @@ class ObjectChooser(object): what_filter (str): an activity bundle_id or a generic mime type as defined in :mod:`sugar3.mime` used to determine which objects will be presented in the object chooser - + filter_type (str): should be one of [None, FILTER_TYPE_GENERIC_MIME, FILTER_TYPE_ACTIVITY, FILTER_TYPE_MIME_BY_ACTIVITY] @@ -155,7 +155,7 @@ class ObjectChooser(object): Examples: chooser = ObjectChooser(self._activity, what_filter='Image') - + chooser = ObjectChooser(parent=self, what_filter=self.get_bundle_id(), filter_type=FILTER_TYPE_ACTIVITY) @@ -192,7 +192,7 @@ class ObjectChooser(object): def run(self): """ Runs the object chooser and displays it. - + Returns: Gtk.ResponseType constant, the response received from displaying the object chooser. diff --git a/src/sugar3/graphics/palettegroup.py b/src/sugar3/graphics/palettegroup.py index 98539feb..9940e355 100644 --- a/src/sugar3/graphics/palettegroup.py +++ b/src/sugar3/graphics/palettegroup.py @@ -36,7 +36,7 @@ def get_group(group_id): def popdown_all(): - for group in _groups.values(): + for group in list(_groups.values()): group.popdown() diff --git a/src/sugar3/graphics/palettemenu.py b/src/sugar3/graphics/palettemenu.py index 23128071..ddc87116 100644 --- a/src/sugar3/graphics/palettemenu.py +++ b/src/sugar3/graphics/palettemenu.py @@ -56,7 +56,7 @@ Example: menu_item.show() def __edit_cb(self, menu_item): - print 'Edit...' + print('Edit...') # Usually the Palette instance is returned in a create_palette function p = ItemPalette() diff --git a/src/sugar3/graphics/palettewindow.py b/src/sugar3/graphics/palettewindow.py index 3253fd00..1cadbb67 100644 --- a/src/sugar3/graphics/palettewindow.py +++ b/src/sugar3/graphics/palettewindow.py @@ -43,6 +43,7 @@ from sugar3.graphics.icon import CellRendererIcon _pointer = None + def _get_pointer_position(widget): global _pointer @@ -53,6 +54,7 @@ def _get_pointer_position(widget): screen, x, y = _pointer.get_position() return (x, y) + def _calculate_gap(a, b): """Helper function to find the gap position and size of widget a""" # Test for each side if the palette and invoker are @@ -420,6 +422,8 @@ class _PaletteWindowWidget(Gtk.Window): self.disconnect_by_func(self.__enter_notify_event_cb) self.disconnect_by_func(self.__leave_notify_event_cb) self.hide() + + if hasattr(_PaletteWindowWidget, 'set_css_name'): _PaletteWindowWidget.set_css_name('palette') @@ -954,7 +958,7 @@ class Invoker(GObject.GObject): dright = screen_area.x + screen_area.width - rect.x - rect.width ih = 0 - + if palette_dim.width == 0: ph = 0 @@ -1315,7 +1319,7 @@ class CursorInvoker(Invoker): self._leave_hid = self._item.connect('leave-notify-event', self.__leave_notify_event_cb) self._release_hid = self._item.connect('button-release-event', - self.__button_release_event_cb) + self.__button_release_event_cb) self._long_pressed_hid = self._long_pressed_controller.connect( 'pressed', self.__long_pressed_event_cb, self._item) @@ -1325,9 +1329,9 @@ class CursorInvoker(Invoker): def detach(self): Invoker.detach(self) - self._item.disconnect(self._enter_hid) - self._item.disconnect(self._leave_hid) - self._item.disconnect(self._release_hid) + self._item.disconnect_by_func(self.__enter_notify_event_cb) + self._item.disconnect_by_func(self.__leave_notify_event_cb) + self._item.disconnect_by_func(self.__button_release_event_cb) self._long_pressed_controller.detach(self._item) self._long_pressed_controller.disconnect(self._long_pressed_hid) diff --git a/src/sugar3/graphics/popwindow.py b/src/sugar3/graphics/popwindow.py deleted file mode 100644 index 5a4a4d9e..00000000 --- a/src/sugar3/graphics/popwindow.py +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright (C) 2016 Abhijit Patel -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -''' -Provide a PopWindow class for pop-up windows. -Making PopWindow containing Gtk.Toolbar which also contains Gtk.Label -and Toolbutton at the end of the Gtk.Toolbar. - -It is possible to change props like size and add more widgets PopWindow -and also to Gtk.Toolbar. - -Example: - .. literalinclude: ..sugar/src/jarabe/view/viewsource.py - .. literalinclude: ..sugar/src/jarabe/view/viewhelp.py -''' -from gettext import gettext as _ -from gi.repository import Gtk -from gi.repository import Gdk -from gi.repository import GdkX11 -from gi.repository import GObject - -from sugar3.graphics import style -from sugar3.graphics.toolbutton import ToolButton - -from jarabe.model import shell - - -class PopWindow(Gtk.Window): - """ - UI interface for activity Pop-up Windows. - PopWindows are the windows that open on the top of the current window. - These pop-up windows don't cover the whole screen. - They contain canvas content, alerts messages, a tray and a - toolbar. - - FULLSCREEN and HALF_WIDTH for setting size of the window. - - Kwargs: - size (int,int): size to be set of the window - window_xid (xlib.Window): xid of the parent window - """ - FULLSCREEN = (Gdk.Screen.width() - style.GRID_CELL_SIZE * 3, - Gdk.Screen.height() - style.GRID_CELL_SIZE * 2) - - HALF_WIDTH = ((Gdk.Screen.height() - style.GRID_CELL_SIZE * 3)/2, - (Gdk.Screen.height() - style.GRID_CELL_SIZE * 2)) - - def __init__(self, window_xid=None, **kwargs): - Gtk.Window.__init__(self, **kwargs) - self._parent_window_xid = window_xid - - self.set_decorated(False) - self.set_position(Gtk.WindowPosition.CENTER_ALWAYS) - self.set_border_width(style.LINE_WIDTH) - self.set_has_resize_grip(False) - self.props.size = self.FULLSCREEN - - self.connect('realize', self.__realize_cb) - self.connect('key-press-event', self.__key_press_event_cb) - self.connect('hide', self.__hide_cb) - - self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - self.add(self._vbox) - self._vbox.show() - - self._title_box = TitleBox() - self._title_box.close_button.connect( - 'clicked', - self.__close_button_clicked_cb) - self._title_box.set_size_request(-1, style.GRID_CELL_SIZE) - - self._vbox.pack_start(self._title_box, False, True, 0) - self._title_box.show() - - # Note: - # Not displaying the pop-up from here instead allowing - # the child class to display the window after modifications - # like chaninging window size, decorating, changing position. - - def set_size(self, size): - width, height = size - self.set_size_request(width, height) - - size = GObject.Property(type=None, setter=set_size) - - def get_title_box(self): - ''' - Getter method for title-box - - Returns: - self._title_box (): Title or Tool Box - ''' - return self._title_box - - title_box = GObject.Property(type=str, getter=get_title_box) - - def get_vbox(self): - ''' - Getter method for canvas - - Returns: - self._vbox (Gtk.Box): canvas - ''' - return self._vbox - vbox = GObject.Property(type=str, getter=get_vbox) - - def __close_button_clicked_cb(self, button): - self.destroy() - - def __key_press_event_cb(self, window, event): - keyname = Gdk.keyval_name(event.keyval) - if keyname == 'Escape': - self.destroy() - - def __realize_cb(self, widget): - self.set_type_hint(Gdk.WindowTypeHint.DIALOG) - window = self.get_window() - window.set_accept_focus(True) - - if self._parent_window_xid is not None: - display = Gdk.Display.get_default() - parent = GdkX11.X11Window.foreign_new_for_display( - display, self._parent_window_xid) - window.set_transient_for(parent) - shell.get_model().push_modal() - - def __hide_cb(self, widget): - shell.get_model().pop_modal() - - def add_view(self, widget, expand=True, fill=True, padding=0): - ''' - Adds child to the vbox. - - Args: - widget (Gtk.Widget): widget to be added - - expand (bool): True if child is to be given extra space allocated - to vbox. - - fill (bool): True if space given to child by the expand option is - actually allocated to child, rather than just padding it. - - padding (int): extra space in pixels to put between child and its - neighbors, over and above the global amount specified - by spacing in vbox. - - Returns: - None - ''' - self._vbox.pack_start(widget, expand, fill, padding) - - -class TitleBox(Gtk.Toolbar): - ''' - Title box at the top of the pop-up window. - Title and close button are added to the box and as needed more widgets - can be added using self.add_widget method. - This box is optional as the inherited class can remove this block by - setting the self._set_title_box to False. - ''' - - def __init__(self): - Gtk.Toolbar.__init__(self) - - self.close_button = ToolButton(icon_name='dialog-cancel') - self.close_button.set_tooltip(_('Close')) - self.insert(self.close_button, -1) - self.close_button.show() - - self._label = Gtk.Label() - self._label.set_alignment(0, 0.5) - - tool_item = Gtk.ToolItem() - tool_item.set_expand(True) - tool_item.add(self._label) - self._label.show() - self.insert(tool_item, 0) - tool_item.show() - - def set_title(self, title): - ''' - setter function for 'title' property. - Args: - title (str): title for the pop-up window - ''' - self._label.set_markup('%s' % title) - self._label.show() - - title = GObject.Property(type=str, setter=set_title) diff --git a/src/sugar3/graphics/progressicon.py b/src/sugar3/graphics/progressicon.py index 32748710..d001efc7 100644 --- a/src/sugar3/graphics/progressicon.py +++ b/src/sugar3/graphics/progressicon.py @@ -41,6 +41,7 @@ class ProgressIcon(Gtk.DrawingArea): fill_color (string): The main (inside) color of progressicon [e.g. fill_color=style.COLOR_BLUE.get_svg() ''' + def __init__(self, icon_name, pixel_size, stroke_color, fill_color, direction='vertical'): Gtk.DrawingArea.__init__(self) diff --git a/src/sugar3/graphics/scrollingdetector.py b/src/sugar3/graphics/scrollingdetector.py index c0b483b2..a533aae0 100644 --- a/src/sugar3/graphics/scrollingdetector.py +++ b/src/sugar3/graphics/scrollingdetector.py @@ -32,18 +32,18 @@ from gi.repository import GLib class ScrollingDetector(GObject.GObject): ''' - The scrolling detector sends signals when a scrolled window is scrolled and + The scrolling detector sends signals when a scrolled window is scrolled and when a scrolled window stops scrolling. Only one `scroll-start` signal will be - emitted until scrolling stops. - + emitted until scrolling stops. + The `scroll-start` signal is emitted when scrolling begins and The `scroll-end` signal is emitted when scrolling ends Neither of these two signals have any arguments - + Args: scrolled_window (Gtk.ScrolledWindow): A GTK scrolled window object for which scrolling is to be detected - + timeout (int): time in milliseconds to establish the interval for which scrolling is detected ''' @@ -65,7 +65,7 @@ class ScrollingDetector(GObject.GObject): Connects scrolling detector to a scrolled window. Detects scrolling when the vertical scrollbar adjustment value is changed - + Should be used to link an instance of a scrolling detector to a Scrolled Window, after setting scrolled_window ''' diff --git a/src/sugar3/graphics/style.py b/src/sugar3/graphics/style.py index 1a5f47f6..8dafd5b7 100644 --- a/src/sugar3/graphics/style.py +++ b/src/sugar3/graphics/style.py @@ -59,6 +59,7 @@ class Font(object): Args: desc (str): a description of the Font object ''' + def __init__(self, desc): self._desc = desc @@ -84,6 +85,7 @@ class Color(object): alpha (double): transparency of color ''' + def __init__(self, color, alpha=1.0): self._r, self._g, self._b = self._html_to_rgb(color) self._a = alpha @@ -112,7 +114,8 @@ class Color(object): ''' Returns string in the standard html Color format (#FFFFFF) ''' - return '#%02x%02x%02x' % (self._r * 255, self._g * 255, self._b * 255) + return '#%02x%02x%02x' % ( + int(self._r * 255), int(self._g * 255), int(self._b * 255)) def _html_to_rgb(self, html_color): ''' diff --git a/src/sugar3/graphics/toolbarbox.py b/src/sugar3/graphics/toolbarbox.py index c813da06..804e2b24 100644 --- a/src/sugar3/graphics/toolbarbox.py +++ b/src/sugar3/graphics/toolbarbox.py @@ -203,6 +203,8 @@ class ToolbarBox(Gtk.VBox): if button == self.expanded_button: self.remove(button.page_widget) self._expanded_button_index = -1 + + if hasattr(ToolbarBox, 'set_css_name'): ToolbarBox.set_css_name('toolbarbox') diff --git a/src/sugar3/graphics/toolbox.py b/src/sugar3/graphics/toolbox.py index 5cd41f11..23843b11 100644 --- a/src/sugar3/graphics/toolbox.py +++ b/src/sugar3/graphics/toolbox.py @@ -32,7 +32,7 @@ class Toolbox(Gtk.VBox): Class to represent the toolbox of an activity. Groups a number of toolbars vertically, which can be accessed using their indices. The current toolbar is the only one displayed. - + Emits `current-toolbar-changed` signal when the current toolbar is changed. This signal takes the current page index as an argument. @@ -71,13 +71,13 @@ class Toolbox(Gtk.VBox): def add_toolbar(self, name, toolbar): ''' Adds a toolbar to this toolbox. Toolbar will be added - to the end of this toolbox, and it's index will be + to the end of this toolbox, and it's index will be 1 greater than the previously added index (index will be 0 if it is the first toolbar added). - + Args: name (string): name of toolbar to be added - + toolbar (.. :class:`Gtk.Toolbar`): Gtk.Toolbar to be appended to this toolbox ''' label = Gtk.Label(label=name) @@ -106,7 +106,7 @@ class Toolbox(Gtk.VBox): def remove_toolbar(self, index): ''' Removes toolbar at the index specified. - + Args: index (int): index of the toolbar to be removed ''' @@ -118,9 +118,9 @@ class Toolbox(Gtk.VBox): def set_current_toolbar(self, index): ''' - Sets the current toolbar to that of the index specified and + Sets the current toolbar to that of the index specified and displays it. - + Args: index (int): index of toolbar to be set as current toolbar ''' diff --git a/src/sugar3/graphics/toolbutton.py b/src/sugar3/graphics/toolbutton.py index e8f4e9ed..8b0031f7 100644 --- a/src/sugar3/graphics/toolbutton.py +++ b/src/sugar3/graphics/toolbutton.py @@ -27,7 +27,7 @@ Example: from sugar3.graphics.toolbutton import ToolButton def __clicked_cb(button): - print "tool button was clicked" + print("tool button was clicked") w = Gtk.Window() w.connect('destroy', Gtk.main_quit) diff --git a/src/sugar3/graphics/tray.py b/src/sugar3/graphics/tray.py index da8dda99..55db31af 100644 --- a/src/sugar3/graphics/tray.py +++ b/src/sugar3/graphics/tray.py @@ -330,6 +330,8 @@ class HTray(Gtk.EventBox): def scroll_to_item(self, item): self._viewport.scroll_to_item(item) + + if hasattr(HTray, 'set_css_name'): HTray.set_css_name('htray') @@ -424,6 +426,8 @@ class VTray(Gtk.EventBox): def scroll_to_item(self, item): self._viewport.scroll_to_item(item) + + if hasattr(VTray, 'set_css_name'): VTray.set_css_name('VTray') diff --git a/src/sugar3/graphics/xocolor.py b/src/sugar3/graphics/xocolor.py index 4c6e33a8..e5d7e4e8 100644 --- a/src/sugar3/graphics/xocolor.py +++ b/src/sugar3/graphics/xocolor.py @@ -20,6 +20,7 @@ This class represents all of the colors that the XO can take on. Each pair of colors represents the fill color and the stroke color ''' +import six import random import logging @@ -210,11 +211,11 @@ colors = [['#B20008', '#FF2B34'], def _parse_string(color_string): ''' Returns array of length 2 of two colors in standard html form of [stroke color, fill color] - + Args: color_string (string): two html format strings separated by a comma ''' - if not isinstance(color_string, (str, unicode)): + if not isinstance(color_string, (six.text_type, six.binary_type)): logging.error('Invalid color string: %r', color_string) return None @@ -233,13 +234,14 @@ def _parse_string(color_string): class XoColor: ''' Defines color for XO - + Args: - color_string (string): two html format strings separated - by a comma, "white", or "insensitive". If color_string - is None, the user's color will be created. If parsed_color - cannot be created, a random color will be used + color_string (string): two html format strings separated + by a comma, "white", or "insensitive". If color_string + is None, the user's color will be created. If parsed_color + cannot be created, a random color will be used ''' + def __init__(self, color_string=None): parsed_color = None @@ -261,7 +263,7 @@ class XoColor: ''' Compares two XO colors by their stroke and fill color Returns 0 if they are equal and -1 if they are unequal - + Args: other (object): other XO color to compare ''' @@ -295,12 +297,12 @@ if __name__ == '__main__': f = open(sys.argv[1], 'r') - print 'colors = [' + print('colors = [') for line in f.readlines(): match = re.match(r'fill: ([A-Z0-9]*) stroke: ([A-Z0-9]*)', line) - print "['#%s', '#%s'], \\" % (match.group(2), match.group(1)) + print("['#%s', '#%s'], \\" % (match.group(2), match.group(1))) - print ']' + print(']') f.close() diff --git a/src/sugar3/logger.py b/src/sugar3/logger.py index 2e01b1cb..43ef5b56 100644 --- a/src/sugar3/logger.py +++ b/src/sugar3/logger.py @@ -20,16 +20,17 @@ STABLE. """ +import six import array import collections import errno import logging import sys import os -import repr as repr_ import decorator import time +from six.moves import reprlib as repr_ from sugar3 import env # Let's keep this module self contained so that it can be easily @@ -104,8 +105,8 @@ def cleanup(): for f in os.listdir(root): os.remove(os.path.join(root, f)) os.rmdir(root) - except OSError, e: - print "Could not remove old logs files %s" % e + except OSError as e: + print("Could not remove old logs files %s" % e) if len(backup_logs) > 0: name = str(int(time.time())) @@ -116,7 +117,7 @@ def cleanup(): source_path = os.path.join(logs_dir, log) dest_path = os.path.join(backup_dir, log) os.rename(source_path, dest_path) - except OSError, e: + except OSError as e: # gracefully deal w/ disk full if e.errno != errno.ENOSPC: raise e @@ -145,7 +146,7 @@ def start(log_filename=None): def write(self, s): try: self._stream.write(s) - except IOError, e: + except IOError as e: # gracefully deal w/ disk full if e.errno != errno.ENOSPC: raise e @@ -153,7 +154,7 @@ def start(log_filename=None): def flush(self): try: self._stream.flush() - except IOError, e: + except IOError as e: # gracefully deal w/ disk full if e.errno != errno.ENOSPC: raise e @@ -177,7 +178,7 @@ def start(log_filename=None): sys.stdout = SafeLogWrapper(sys.stdout) sys.stderr = SafeLogWrapper(sys.stderr) - except OSError, e: + except OSError as e: # if we're out of space, just continue if e.errno != errno.ENOSPC: raise e @@ -188,13 +189,15 @@ def start(log_filename=None): class TraceRepr(repr_.Repr): # better handling of subclasses of basic types, e.g. for DBus - _TYPES = [int, long, bool, tuple, list, array.array, set, frozenset, + _TYPES = [int, bool, tuple, list, array.array, set, frozenset, collections.deque, dict, str] + if six.PY2: + _TYPES.append(long) def repr1(self, x, level): for t in self._TYPES: if isinstance(x, t): - return getattr(self, 'repr_'+t.__name__)(x, level) + return getattr(self, 'repr_' + t.__name__)(x, level) return repr_.Repr.repr1(self, x, level) @@ -232,14 +235,14 @@ def trace(logger=None, logger_name=None, skip_args=None, skip_kwargs=None, [trace_repr.repr(a) for (idx, a) in enumerate(args) if idx not in skip_args] + ['%s=%s' % (k, trace_repr.repr(v)) - for (k, v) in kwargs.items() if k not in skip_kwargs]) + for (k, v) in list(kwargs.items()) if k not in skip_kwargs]) trace_logger.log(TRACE, "%s(%s) invoked", f.__name__, params_formatted) try: res = f(*args, **kwargs) - except: + except BaseException: trace_logger.exception("Exception occurred in %s" % f.__name__) raise diff --git a/src/sugar3/mime.py b/src/sugar3/mime.py index cd30fa70..a2797d0d 100644 --- a/src/sugar3/mime.py +++ b/src/sugar3/mime.py @@ -32,7 +32,9 @@ from gi.repository import GLib from gi.repository import GdkPixbuf from gi.repository import Gio -_ = lambda msg: gettext.dgettext('sugar-toolkit-gtk3', msg) + +def _(msg): return gettext.dgettext('sugar-toolkit-gtk3', msg) + GENERIC_TYPE_TEXT = 'Text' GENERIC_TYPE_IMAGE = 'Image' @@ -185,14 +187,14 @@ def get_mime_parents(mime_type): with open(subclasses_path) as parents_file: for line in parents_file: subclass, parent = line.split() - if subclass not in _subclasses.keys(): + if subclass not in list(_subclasses.keys()): _subclasses[subclass] = [parent] else: _subclasses[subclass].append(parent) _subclasses_timestamps = timestamps - if mime_type in _subclasses.keys(): + if mime_type in list(_subclasses.keys()): return _subclasses[mime_type] else: return [] diff --git a/src/sugar3/network.py b/src/sugar3/network.py index 771a3cae..6afa4f8a 100644 --- a/src/sugar3/network.py +++ b/src/sugar3/network.py @@ -21,14 +21,14 @@ STABLE. import os import threading -import urllib +from six.moves import urllib import fcntl import tempfile from gi.repository import GObject from gi.repository import GLib -import SimpleHTTPServer -import SocketServer +from six.moves import SimpleHTTPServer +from six.moves import socketserver __authinfos = {} @@ -46,7 +46,7 @@ def _del_authinfo(): del __authinfos[threading.currentThread()] -class GlibTCPServer(SocketServer.TCPServer): +class GlibTCPServer(socketserver.TCPServer): """GlibTCPServer Integrate socket accept into glib mainloop. @@ -56,7 +56,7 @@ class GlibTCPServer(SocketServer.TCPServer): request_queue_size = 20 def __init__(self, server_address, RequestHandlerClass): - SocketServer.TCPServer.__init__(self, server_address, + socketserver.TCPServer.__init__(self, server_address, RequestHandlerClass) self.socket.setblocking(0) # Set nonblocking @@ -212,7 +212,7 @@ class GlibURLDownloader(GObject.GObject): GObject.GObject.__init__(self) def start(self, destfile=None, destfd=None): - self._info = urllib.urlopen(self._url) + self._info = urllib.request.urlopen(self._url) self._outf = None self._fname = None if destfd and not destfile: @@ -226,14 +226,14 @@ class GlibURLDownloader(GObject.GObject): self._outf = destfd else: self._outf = os.open(self._fname, os.O_RDWR | - os.O_TRUNC | os.O_CREAT, 0644) + os.O_TRUNC | os.O_CREAT, 0o644) else: fname = self._get_filename_from_headers(self._info.headers) self._suggested_fname = fname - garbage_, path = urllib.splittype(self._url) - garbage_, path = urllib.splithost(path or "") - path, garbage_ = urllib.splitquery(path or "") - path, garbage_ = urllib.splitattr(path or "") + garbage_, path = urllib.parse.splittype(self._url) + garbage_, path = urllib.parse.splithost(path or "") + path, garbage_ = urllib.parse.splitquery(path or "") + path, garbage_ = urllib.parse.splitattr(path or "") suffix = os.path.splitext(path)[1] (self._outf, self._fname) = tempfile.mkstemp(suffix=suffix, dir=self._destdir) @@ -291,7 +291,7 @@ class GlibURLDownloader(GObject.GObject): self.cleanup() self.emit('finished', self._fname, self._suggested_fname) return False - except Exception, err: + except Exception as err: self.cleanup(remove=True) self.emit('error', 'Error downloading file: %r' % err) return False diff --git a/src/sugar3/presence/activity.py b/src/sugar3/presence/activity.py index f07aab82..6097b837 100644 --- a/src/sugar3/presence/activity.py +++ b/src/sugar3/presence/activity.py @@ -21,6 +21,7 @@ STABLE. """ +import six import logging from functools import partial @@ -124,6 +125,11 @@ class Activity(GObject.GObject): def _start_tracking_properties(self): bus = dbus.SessionBus() + arg_dict = dict(reply_handler=self.__got_properties_cb, + error_handler=self.__error_handler_cb) + if six.PY2: + arg_dict = arg_dict.update(utf8_strings=True) + self._get_properties_call = bus.call_async( self.telepathy_conn.requested_bus_name, self.telepathy_conn.object_path, @@ -131,9 +137,7 @@ class Activity(GObject.GObject): 'GetProperties', 'u', (self.room_handle,), - reply_handler=self.__got_properties_cb, - error_handler=self.__error_handler_cb, - utf8_strings=True) + arg_dict) # As only one Activity instance is needed per activity process, # we can afford listening to ActivityPropertiesChanged like this. @@ -144,7 +148,7 @@ class Activity(GObject.GObject): def __activity_properties_changed_cb(self, room_handle, properties): _logger.debug('%r: Activity properties changed to %r' % (self, - properties)) + properties)) self._update_properties(properties) def __got_properties_cb(self, properties): @@ -239,7 +243,7 @@ class Activity(GObject.GObject): returns list of presence Buddy objects that we can successfully create from the buddy object paths that PS has for this activity. """ - return self._joined_buddies.values() + return list(self._joined_buddies.values()) def get_buddy_by_handle(self, handle): """Retrieve the Buddy object given a telepathy handle. @@ -300,8 +304,9 @@ class Activity(GObject.GObject): channel.connect_to_signal('Closed', self.__text_channel_closed_cb) def __get_all_members_cb(self, members, local_pending, remote_pending): - _logger.debug('__get_all_members_cb %r %r' % (members, - self._text_channel_group_flags)) + _logger.debug( + '__get_all_members_cb %r %r' % + (members, self._text_channel_group_flags)) if self._channel_self_handle in members: members.remove(self._channel_self_handle) @@ -631,8 +636,9 @@ class _JoinCommand(_BaseCommand): self._add_self_to_channel() def __text_channel_group_flags_changed_cb(self, added, removed): - _logger.debug('__text_channel_group_flags_changed_cb %r %r' % (added, - removed)) + _logger.debug( + '__text_channel_group_flags_changed_cb %r %r' % + (added, removed)) self.text_channel_group_flags |= added self.text_channel_group_flags &= ~removed diff --git a/src/sugar3/presence/buddy.py b/src/sugar3/presence/buddy.py index 6f85ae89..2e0190c0 100644 --- a/src/sugar3/presence/buddy.py +++ b/src/sugar3/presence/buddy.py @@ -22,7 +22,7 @@ STABLE. """ import logging - +import six from gi.repository import GObject import dbus from telepathy.interfaces import CONNECTION, \ @@ -103,7 +103,7 @@ class BaseBuddy(GObject.GObject): def get_current_activity(self): if self._current_activity is None: return None - for activity in self._activities.values(): + for activity in list(self._activities.values()): if activity.props.id == self._current_activity: return activity return None @@ -164,6 +164,12 @@ class Buddy(BaseBuddy): dbus_interface=CONNECTION) self.contact_handle = handles[0] + arg_dict = dict(reply_handler=self.__got_properties_cb, + error_handler=self.__error_handler_cb, + byte_arrays = True) + if six.PY2: + arg_dict = arg_dict.update(utf8_strings=True) + self._get_properties_call = bus.call_async( connection_name, connection.object_path, @@ -171,10 +177,7 @@ class Buddy(BaseBuddy): 'GetProperties', 'u', (self.contact_handle,), - reply_handler=self.__got_properties_cb, - error_handler=self.__error_handler_cb, - utf8_strings=True, - byte_arrays=True) + arg_dict) self._get_attributes_call = bus.call_async( connection_name, @@ -245,4 +248,3 @@ class Owner(BaseBuddy): self.props.nick = get_nick_name() self.props.color = get_color().to_string() - diff --git a/src/sugar3/presence/connectionmanager.py b/src/sugar3/presence/connectionmanager.py index 325c200c..dbb957ca 100644 --- a/src/sugar3/presence/connectionmanager.py +++ b/src/sugar3/presence/connectionmanager.py @@ -92,7 +92,8 @@ class ConnectionManager(object): def get_preferred_connection(self): best_connection = None, None - for account_path, connection in self._connections_per_account.items(): + for account_path, connection in list( + self._connections_per_account.items()): if 'salut' in account_path and connection.connected: best_connection = account_path, connection.connection elif 'gabble' in account_path and connection.connected: @@ -107,7 +108,8 @@ class ConnectionManager(object): return self._connections_per_account def get_account_for_connection(self, connection_path): - for account_path, connection in self._connections_per_account.items(): + for account_path, connection in list( + self._connections_per_account.items()): if connection.connection.object_path == connection_path: return account_path return None diff --git a/src/sugar3/presence/presenceservice.py b/src/sugar3/presence/presenceservice.py index 8b66b0e5..2f9f8510 100644 --- a/src/sugar3/presence/presenceservice.py +++ b/src/sugar3/presence/presenceservice.py @@ -78,7 +78,8 @@ class PresenceService(GObject.GObject): connection_manager = get_connection_manager() connections_per_account = \ connection_manager.get_connections_per_account() - for account_path, connection in connections_per_account.items(): + for account_path, connection in list( + connections_per_account.items()): if not connection.connected: continue logging.debug('Calling GetActivity on %s' % account_path) @@ -86,13 +87,13 @@ class PresenceService(GObject.GObject): room_handle = connection.connection.GetActivity( activity_id, dbus_interface=CONN_INTERFACE_ACTIVITY_PROPERTIES) - except dbus.exceptions.DBusException, e: + except dbus.exceptions.DBusException as e: name = 'org.freedesktop.Telepathy.Error.NotAvailable' if e.get_dbus_name() == name: logging.debug("There's no shared activity with the id " "%s" % activity_id) elif e.get_dbus_name() == \ - 'org.freedesktop.DBus.Error.UnknownMethod': + 'org.freedesktop.DBus.Error.UnknownMethod': logging.warning( 'Telepathy Account %r does not support ' 'Sugar collaboration', account_path) diff --git a/src/sugar3/presence/tubeconn.py b/src/sugar3/presence/tubeconn.py index 9a496d98..8b1e2ff9 100644 --- a/src/sugar3/presence/tubeconn.py +++ b/src/sugar3/presence/tubeconn.py @@ -24,6 +24,7 @@ __all__ = ('TubeConnection', ) __docformat__ = 'reStructuredText' +import six import logging from dbus.connection import Connection @@ -77,7 +78,9 @@ class TubeConnection(Connection): def close(self): self._dbus_names_changed_match.remove() - self._on_dbus_names_changed(self.tube_id, (), self.participants.keys()) + self._on_dbus_names_changed( + self.tube_id, (), list( + self.participants.keys())) super(TubeConnection, self).close() def _on_get_dbus_names_reply(self, names): @@ -111,6 +114,6 @@ class TubeConnection(Connection): # GetDBusNames already returned: fake a participant add event # immediately added = [] - for k, v in self.participants.iteritems(): + for k, v in six.iteritems(self.participants): added.append((k, v)) callback(added, []) diff --git a/src/sugar3/profile.py b/src/sugar3/profile.py index 818afe05..e46943fd 100644 --- a/src/sugar3/profile.py +++ b/src/sugar3/profile.py @@ -21,7 +21,8 @@ from gi.repository import Gio import os import logging -from ConfigParser import ConfigParser + +from six.moves.configparser import ConfigParser from sugar3 import env from sugar3 import util diff --git a/src/sugar3/speech.py b/src/sugar3/speech.py index fd2c5b5f..19416043 100644 --- a/src/sugar3/speech.py +++ b/src/sugar3/speech.py @@ -33,7 +33,7 @@ try: from gi.repository import Gst Gst.init(None) Gst.parse_launch('espeak') -except: +except BaseException: logging.error('Gst or the espeak plugin is not installed in the system.') _HAS_GST = False @@ -268,7 +268,12 @@ class SpeechManager(GObject.GObject): else: voice_name = self._player.get_all_voices()[lang_code] if text: - logging.error('PLAYING %r lang %r pitch %r rate %r', text, voice_name, pitch, rate) + logging.error( + 'PLAYING %r lang %r pitch %r rate %r', + text, + voice_name, + pitch, + rate) self._player.speak(pitch, rate, voice_name, text) def say_selected_text(self): diff --git a/src/sugar3/test/Makefile.am b/src/sugar3/test/Makefile.am index 2ccac022..6d77c2fc 100644 --- a/src/sugar3/test/Makefile.am +++ b/src/sugar3/test/Makefile.am @@ -3,4 +3,4 @@ sugar_PYTHON = \ __init__.py \ discover.py \ uitree.py \ - unittest.py + _unittest.py diff --git a/src/sugar3/test/unittest.py b/src/sugar3/test/_unittest.py similarity index 93% rename from src/sugar3/test/unittest.py rename to src/sugar3/test/_unittest.py index d3e65ee2..6c51d7ee 100644 --- a/src/sugar3/test/unittest.py +++ b/src/sugar3/test/_unittest.py @@ -18,8 +18,6 @@ UNSTABLE. """ -from __future__ import absolute_import - import logging import os import unittest @@ -52,11 +50,11 @@ class UITestCase(unittest.TestCase): @contextmanager def run_view(self, name): view_path = os.path.join("views", "%s.py" % name) - process = subprocess.Popen(["python", view_path]) + process = subprocess.Popen(["python3", view_path]) try: yield - except: + except BaseException: logging.debug(uitree.get_root().dump()) raise finally: @@ -77,12 +75,12 @@ class UITestCase(unittest.TestCase): cmd += options process = subprocess.Popen(cmd) else: - print "No bundle_id specified." + print("No bundle_id specified.") return try: yield - except: + except BaseException: logging.debug(uitree.get_root().dump()) raise finally: diff --git a/src/sugar3/test/discover.py b/src/sugar3/test/discover.py index 8de28f8c..dbf898d1 100644 --- a/src/sugar3/test/discover.py +++ b/src/sugar3/test/discover.py @@ -18,8 +18,6 @@ UNSTABLE. """ -from __future__ import absolute_import - import argparse import sys import os diff --git a/src/sugar3/test/uitree.py b/src/sugar3/test/uitree.py index a4e8e710..3da33c1c 100644 --- a/src/sugar3/test/uitree.py +++ b/src/sugar3/test/uitree.py @@ -44,7 +44,7 @@ def _retry_find(func): try: result = func(*args, **kwargs) - except GLib.GError, e: + except GLib.GError as e: # The application is not responding, try again if e.code == Atspi.Error.IPC: continue diff --git a/src/sugar3/util.py b/src/sugar3/util.py index f44f250c..45cbd368 100644 --- a/src/sugar3/util.py +++ b/src/sugar3/util.py @@ -20,6 +20,7 @@ UNSTABLE. We have been adding helpers randomly to this module. """ +import six import os import time import hashlib @@ -31,20 +32,26 @@ import logging import atexit -_ = lambda msg: gettext.dgettext('sugar-toolkit-gtk3', msg) +def _(msg): return gettext.dgettext('sugar-toolkit-gtk3', msg) def printable_hash(in_hash): """Convert binary hash data into printable characters.""" printable = "" for char in in_hash: - printable = printable + binascii.b2a_hex(char) + if six.PY3: + char = bytes([char]) + printable = printable + binascii.b2a_hex(char).decode() + else: + printable = printable + binascii.b2a_hex(char) return printable def sha_data(data): """sha1 hash some bytes.""" sha_hash = hashlib.sha1() + if six.PY3: + data = data.encode('utf-8') sha_hash.update(data) return sha_hash.digest() @@ -81,7 +88,7 @@ def is_hex(s): def validate_activity_id(actid): """Validate an activity ID.""" - if not isinstance(actid, (str, unicode)): + if not isinstance(actid, (six.binary_type, six.text_type)): return False if len(actid) != ACTIVITY_ID_LEN: return False @@ -204,7 +211,7 @@ class LRU: yield j def keys(self): - return self.d.keys() + return list(self.d.keys()) units = [['%d year', '%d years', 356 * 24 * 60 * 60], @@ -331,13 +338,14 @@ class TempFilePath(str): def _cleanup_temp_files(): logging.debug('_cleanup_temp_files') - for path in _tracked_paths.keys(): + for path in list(_tracked_paths.keys()): try: os.unlink(path) - except: + except BaseException: # pylint: disable=W0702 logging.exception('Exception occurred in _cleanup_temp_files') + atexit.register(_cleanup_temp_files) diff --git a/tests/data/sample.activity/activity/activity.info b/tests/data/sample.activity/activity/activity.info index 4cf7f755..820c07a3 100644 --- a/tests/data/sample.activity/activity/activity.info +++ b/tests/data/sample.activity/activity/activity.info @@ -2,6 +2,6 @@ name = Sample activity_version = 1 bundle_id = org.sugarlabs.Sample -exec = sugar-activity activity.SampleActivity +exec = sugar-activity3 activity.SampleActivity icon = activity-sample license = GPLv2+ diff --git a/tests/data/sample.activity/setup.py b/tests/data/sample.activity/setup.py index 9483050b..191204bd 100755 --- a/tests/data/sample.activity/setup.py +++ b/tests/data/sample.activity/setup.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 from sugar3.activity import bundlebuilder diff --git a/tests/graphics/progressicon.py b/tests/graphics/progressicon.py index a922749c..b756d2fa 100644 --- a/tests/graphics/progressicon.py +++ b/tests/graphics/progressicon.py @@ -59,6 +59,7 @@ def timeout_cb(): return False return True + GLib.timeout_add(50, timeout_cb) if __name__ == '__main__': diff --git a/tests/test_mime.py b/tests/test_mime.py index 01896e3c..9e478de8 100644 --- a/tests/test_mime.py +++ b/tests/test_mime.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (C) 2006, Red Hat, Inc. # Copyright (C) 2007, One Laptop Per Child diff --git a/tests/test_uitree.py b/tests/test_uitree.py index 9a3f63b1..b7c7da49 100644 --- a/tests/test_uitree.py +++ b/tests/test_uitree.py @@ -23,7 +23,7 @@ from sugar3.test import uitree class TestUITree(unittest.TestCase): def test_tree(self): - process = subprocess.Popen(["python", __file__, "show_window1"]) + process = subprocess.Popen(["python3", __file__, "show_window1"]) try: root = uitree.get_root() @@ -49,5 +49,6 @@ def show_window1(): Gtk.main() + if __name__ == '__main__': globals()[sys.argv[1]]()