diff options
Diffstat (limited to 'src')
91 files changed, 1622 insertions, 1047 deletions
diff --git a/src/jarabe/__init__.py b/src/jarabe/__init__.py index 41b4b1c..ed2f639 100644 --- a/src/jarabe/__init__.py +++ b/src/jarabe/__init__.py @@ -23,4 +23,3 @@ refer to a command-line "shell" interface. # 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 - diff --git a/src/jarabe/config.py.in b/src/jarabe/config.py.in index 6c418e9..d22ee9a 100644 --- a/src/jarabe/config.py.in +++ b/src/jarabe/config.py.in @@ -14,7 +14,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# pylint: disable-msg=C0301 +# pylint: disable=C0301 prefix = '@prefix@' data_path = '@prefix@/share/sugar/data' diff --git a/src/jarabe/controlpanel/__init__.py b/src/jarabe/controlpanel/__init__.py index a9dd95a..85f6a24 100644 --- a/src/jarabe/controlpanel/__init__.py +++ b/src/jarabe/controlpanel/__init__.py @@ -13,4 +13,3 @@ # 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 - diff --git a/src/jarabe/controlpanel/cmd.py b/src/jarabe/controlpanel/cmd.py index 7144b33..fe8f1a4 100644 --- a/src/jarabe/controlpanel/cmd.py +++ b/src/jarabe/controlpanel/cmd.py @@ -18,20 +18,21 @@ import sys import getopt import os from gettext import gettext as _ -import traceback import logging from jarabe import config + _RESTART = 1 -_same_option_warning = _("sugar-control-panel: WARNING, found more than" - " one option with the same name: %s module: %r") -_no_option_error = _("sugar-control-panel: key=%s not an available option") -_general_error = _("sugar-control-panel: %s") +_same_option_warning = _('sugar-control-panel: WARNING, found more than one' + ' option with the same name: %s module: %r') +_no_option_error = _('sugar-control-panel: key=%s not an available option') +_general_error = _('sugar-control-panel: %s') + def cmd_help(): - '''Print the help to the screen''' + """Print the help to the screen""" # TRANS: Translators, there's a empty line at the end of this string, # which must appear in the translated string (msgstr) as well. print _('Usage: sugar-control-panel [ option ] key [ args ... ] \n\ @@ -45,14 +46,16 @@ def cmd_help(): -c key clear the current value for the key \n\ ') + def note_restart(): - '''Instructions how to restart sugar''' + """Instructions how to restart sugar""" print _('To apply your changes you have to restart sugar.\n' + 'Hit ctrl+alt+erase on the keyboard to trigger a restart.') + def load_modules(): - '''Build a list of pointers to available modules and import them. - ''' + """Build a list of pointers to available modules and import them. + """ modules = [] path = os.path.join(config.ext_path, 'cpsection') @@ -65,16 +68,16 @@ def load_modules(): module = __import__('.'.join(('cpsection', item, 'model')), globals(), locals(), ['model']) except Exception: - logging.error('Exception while loading extension:\n' + \ - ''.join(traceback.format_exception(*sys.exc_info()))) + logging.exception('Exception while loading extension:') else: modules.append(module) return modules + def main(): try: - options, args = getopt.getopt(sys.argv[1:], "h:s:g:c:l", []) + options, args = getopt.getopt(sys.argv[1:], 'h:s:g:c:l', []) except getopt.GetoptError: cmd_help() sys.exit(2) @@ -87,7 +90,7 @@ def main(): for option, key in options: found = 0 - if option in ("-h"): + if option in ('-h'): for module in modules: method = getattr(module, 'set_' + key, None) if method: @@ -98,7 +101,7 @@ def main(): print _(_same_option_warning % (key, module)) if found == 0: print _(_no_option_error % key) - if option in ("-l"): + if option in ('-l'): for module in modules: methods = dir(module) print '%s:' % module.__name__.split('.')[1] @@ -106,9 +109,9 @@ def main(): if method.startswith('get_'): print ' %s' % method[4:] elif method.startswith('clear_'): - print " %s (use the -c argument with this option)" \ + print ' %s (use the -c argument with this option)' \ % method[6:] - if option in ("-g"): + if option in ('-g'): for module in modules: method = getattr(module, 'print_' + key, None) if method: @@ -122,7 +125,7 @@ def main(): print _(_same_option_warning % (key, module)) if found == 0: print _(_no_option_error % key) - if option in ("-s"): + if option in ('-s'): for module in modules: method = getattr(module, 'set_' + key, None) if method: @@ -139,7 +142,7 @@ def main(): print _(_same_option_warning % (key, module)) if found == 0: print _(_no_option_error % key) - if option in ("-c"): + if option in ('-c'): for module in modules: method = getattr(module, 'clear_' + key, None) if method: diff --git a/src/jarabe/controlpanel/gui.py b/src/jarabe/controlpanel/gui.py index 51d9820..2f55951 100644 --- a/src/jarabe/controlpanel/gui.py +++ b/src/jarabe/controlpanel/gui.py @@ -17,8 +17,6 @@ import os import logging from gettext import gettext as _ -import sys -import traceback import gobject import gtk @@ -32,8 +30,9 @@ from jarabe.controlpanel.toolbar import MainToolbar from jarabe.controlpanel.toolbar import SectionToolbar from jarabe import config + _logger = logging.getLogger('ControlPanel') -_MAX_COLUMNS = 5 + class ControlPanel(gtk.Window): __gtype_name__ = 'SugarControlPanel' @@ -41,6 +40,9 @@ class ControlPanel(gtk.Window): def __init__(self): gtk.Window.__init__(self) + self._max_columns = int(0.285 * (float(gtk.gdk.screen_width()) / + style.GRID_CELL_SIZE - 3)) + self.set_border_width(style.LINE_WIDTH) offset = style.GRID_CELL_SIZE width = gtk.gdk.screen_width() - offset * 2 @@ -74,7 +76,7 @@ class ControlPanel(gtk.Window): self.add(self._vbox) self._vbox.show() - self.connect("realize", self.__realize_cb) + self.connect('realize', self.__realize_cb) self._options = self._get_options() self._current_option = None @@ -110,6 +112,7 @@ class ControlPanel(gtk.Window): self._table = gtk.Table() self._table.set_col_spacings(style.GRID_CELL_SIZE) + self._table.set_row_spacings(style.GRID_CELL_SIZE) self._table.set_border_width(style.GRID_CELL_SIZE) self._scrolledwindow = gtk.ScrolledWindow() @@ -134,8 +137,17 @@ class ControlPanel(gtk.Window): except ImportError: del self._options['keyboard'] - row = 0 - column = 2 + # If the screen width only supports two columns, start + # placing from the second row. + if self._max_columns == 2: + row = 1 + column = 0 + else: + # About Me and About my computer are hardcoded below to use the + # first two slots so we need to leave them free. + row = 0 + column = 2 + options = self._options.keys() options.sort() @@ -157,7 +169,7 @@ class ControlPanel(gtk.Window): column, column + 1, row, row + 1) column += 1 - if column == _MAX_COLUMNS: + if column == self._max_columns: column = 0 row += 1 @@ -214,11 +226,16 @@ class ControlPanel(gtk.Window): globals(), locals(), ['model']) model = ModelWrapper(mod) - self._section_view = view_class(model, - self._options[option]['alerts']) + try: + self.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + self._section_view = view_class(model, + self._options[option]['alerts']) + + self._set_canvas(self._section_view) + self._section_view.show() + finally: + self.get_window().set_cursor(None) - self._set_canvas(self._section_view) - self._section_view.show() self._section_view.connect('notify::is-valid', self.__valid_section_cb) self._section_view.connect('request-close', @@ -227,13 +244,13 @@ class ControlPanel(gtk.Window): style.COLOR_WHITE.get_gdk_color()) def set_section_view_auto_close(self): - '''Automatically close the control panel if there is "nothing to do" - ''' + """Automatically close the control panel if there is "nothing to do" + """ self._section_view.auto_close = True def _get_options(self): - '''Get the available option information from the extensions - ''' + """Get the available option information from the extensions + """ options = {} path = os.path.join(config.ext_path, 'cpsection') @@ -259,11 +276,9 @@ class ControlPanel(gtk.Window): keywords.append(item) options[item]['keywords'] = keywords else: - _logger.error('There is no CLASS constant specifieds ' \ - 'in the view file \'%s\'.' % item) + _logger.error('no CLASS attribute in %r', item) except Exception: - logging.error('Exception while loading extension:\n' + \ - ''.join(traceback.format_exception(*sys.exc_info()))) + logging.exception('Exception while loading extension:') return options @@ -333,6 +348,7 @@ class ControlPanel(gtk.Window): section_is_valid = section_view.props.is_valid self._section_toolbar.accept_button.set_sensitive(section_is_valid) + class ModelWrapper(object): def __init__(self, module): self._module = module @@ -360,18 +376,15 @@ class ModelWrapper(object): except Exception, detail: _logger.debug('Error undo option: %s', detail) + class _SectionIcon(gtk.EventBox): - __gtype_name__ = "SugarSectionIcon" + __gtype_name__ = 'SugarSectionIcon' __gproperties__ = { - 'icon-name' : (str, None, None, None, - gobject.PARAM_READWRITE), - 'pixel-size' : (object, None, None, - gobject.PARAM_READWRITE), - 'xo-color' : (object, None, None, - gobject.PARAM_READWRITE), - 'title' : (str, None, None, None, - gobject.PARAM_READWRITE) + 'icon-name': (str, None, None, None, gobject.PARAM_READWRITE), + 'pixel-size': (object, None, None, gobject.PARAM_READWRITE), + 'xo-color': (object, None, None, gobject.PARAM_READWRITE), + 'title': (str, None, None, None, gobject.PARAM_READWRITE), } def __init__(self, **kwargs): diff --git a/src/jarabe/controlpanel/inlinealert.py b/src/jarabe/controlpanel/inlinealert.py index b1880da..f970af4 100644 --- a/src/jarabe/controlpanel/inlinealert.py +++ b/src/jarabe/controlpanel/inlinealert.py @@ -21,6 +21,7 @@ import pango from sugar.graphics import style from sugar.graphics.icon import Icon + class InlineAlert(gtk.HBox): """UI interface for Inline alerts @@ -36,11 +37,9 @@ class InlineAlert(gtk.HBox): __gtype_name__ = 'SugarInlineAlert' __gproperties__ = { - 'msg' : (str, None, None, None, - gobject.PARAM_READWRITE), - 'icon' : (object, None, None, - gobject.PARAM_WRITABLE) - } + 'msg': (str, None, None, None, gobject.PARAM_READWRITE), + 'icon': (object, None, None, gobject.PARAM_WRITABLE), + } def __init__(self, **kwargs): @@ -80,4 +79,3 @@ class InlineAlert(gtk.HBox): def do_get_property(self, pspec): if pspec.name == 'msg': return self._msg - diff --git a/src/jarabe/controlpanel/sectionview.py b/src/jarabe/controlpanel/sectionview.py index 4de27a2..4b5751f 100644 --- a/src/jarabe/controlpanel/sectionview.py +++ b/src/jarabe/controlpanel/sectionview.py @@ -18,18 +18,17 @@ import gobject import gtk from gettext import gettext as _ + class SectionView(gtk.VBox): __gtype_name__ = 'SugarSectionView' __gsignals__ = { - 'request-close': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])) - } + 'request-close': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + } __gproperties__ = { - 'is_valid' : (bool, None, None, True, - gobject.PARAM_READWRITE) - } + 'is_valid': (bool, None, None, True, gobject.PARAM_READWRITE), + } _APPLY_TIMEOUT = 1000 @@ -51,5 +50,5 @@ class SectionView(gtk.VBox): return self._is_valid def undo(self): - '''Undo here the changes that have been made in this section.''' + """Undo here the changes that have been made in this section.""" pass diff --git a/src/jarabe/controlpanel/toolbar.py b/src/jarabe/controlpanel/toolbar.py index 320a8eb..fca34a0 100644 --- a/src/jarabe/controlpanel/toolbar.py +++ b/src/jarabe/controlpanel/toolbar.py @@ -25,6 +25,7 @@ from sugar.graphics.toolbutton import ToolButton from sugar.graphics import iconentry from sugar.graphics import style + class MainToolbar(gtk.Toolbar): """ Main toolbar of the control panel """ @@ -36,8 +37,9 @@ class MainToolbar(gtk.Toolbar): ([])), 'search-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([str])) + ([str])), } + def __init__(self): gtk.Toolbar.__init__(self) @@ -83,6 +85,7 @@ class MainToolbar(gtk.Toolbar): def __stop_clicked_cb(self, button): self.emit('stop-clicked') + class SectionToolbar(gtk.Toolbar): """ Toolbar of the sections of the control panel """ @@ -94,8 +97,9 @@ class SectionToolbar(gtk.Toolbar): ([])), 'accept-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([])) + ([])), } + def __init__(self): gtk.Toolbar.__init__(self) @@ -154,4 +158,3 @@ class SectionToolbar(gtk.Toolbar): def __accept_button_clicked_cb(self, widget, data=None): self.emit('accept-clicked') - diff --git a/src/jarabe/desktop/__init__.py b/src/jarabe/desktop/__init__.py index a9dd95a..85f6a24 100644 --- a/src/jarabe/desktop/__init__.py +++ b/src/jarabe/desktop/__init__.py @@ -13,4 +13,3 @@ # 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 - diff --git a/src/jarabe/desktop/activitieslist.py b/src/jarabe/desktop/activitieslist.py index 56b3b5f..0370ef3 100644 --- a/src/jarabe/desktop/activitieslist.py +++ b/src/jarabe/desktop/activitieslist.py @@ -30,20 +30,18 @@ from sugar.graphics.icon import Icon, CellRendererIcon from sugar.graphics.xocolor import XoColor from sugar.graphics.menuitem import MenuItem from sugar.graphics.alert import Alert -from sugar.activity import activityfactory -from sugar.activity.activityhandle import ActivityHandle from jarabe.model import bundleregistry from jarabe.view.palettes import ActivityPalette -from jarabe.view import launcher from jarabe.journal import misc + class ActivitiesTreeView(gtk.TreeView): __gtype_name__ = 'SugarActivitiesTreeView' __gsignals__ = { - 'erase-activated' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([str])) + 'erase-activated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str])), } def __init__(self): @@ -154,6 +152,7 @@ class ActivitiesTreeView(gtk.TreeView): title = model[tree_iter][ListModel.COLUMN_TITLE] return title is not None and title.lower().find(self._query) > -1 + class ListModel(gtk.TreeModelSort): __gtype_name__ = 'SugarListModel' @@ -167,7 +166,7 @@ class ListModel(gtk.TreeModelSort): COLUMN_DATE_TEXT = 7 def __init__(self): - self._model = gtk.ListStore(str, bool, str, str, int, str, int, str) + self._model = gtk.ListStore(str, bool, str, str, str, str, int, str) self._model_filter = self._model.filter_new() gtk.TreeModelSort.__init__(self, self._model_filter) @@ -238,6 +237,7 @@ class ListModel(gtk.TreeModelSort): def refilter(self): self._model_filter.refilter() + class CellRendererFavorite(CellRendererIcon): __gtype_name__ = 'SugarCellRendererFavorite' @@ -252,12 +252,13 @@ class CellRendererFavorite(CellRendererIcon): self.props.prelit_stroke_color = style.COLOR_BUTTON_GREY.get_svg() self.props.prelit_fill_color = style.COLOR_BUTTON_GREY.get_svg() + class CellRendererActivityIcon(CellRendererIcon): __gtype_name__ = 'SugarCellRendererActivityIcon' __gsignals__ = { - 'erase-activated' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([str])) + 'erase-activated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str])), } def __init__(self, tree_view): @@ -290,6 +291,7 @@ class CellRendererActivityIcon(CellRendererIcon): def __erase_activated_cb(self, palette, bundle_id): self.emit('erase-activated', bundle_id) + class ActivitiesList(gtk.VBox): __gtype_name__ = 'SugarActivitiesList' @@ -373,12 +375,13 @@ class ActivitiesList(gtk.VBox): bundle = registry.get_bundle(bundle_id) registry.uninstall(bundle, delete_profile=True) + class ActivityListPalette(ActivityPalette): __gtype_name__ = 'SugarActivityListPalette' __gsignals__ = { - 'erase-activated' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([str])) + 'erase-activated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str])), } def __init__(self, activity_info): @@ -417,8 +420,8 @@ class ActivityListPalette(ActivityPalette): menu_item.show() if not os.access(activity_info.get_path(), os.W_OK) or \ - registry.is_activity_protected(self._bundle_id): - menu_item.props.sensitive = False + registry.is_activity_protected(self._bundle_id): + menu_item.props.sensitive = False def __destroy_cb(self, palette): self.disconnect(self._activity_changed_sid) @@ -432,7 +435,7 @@ class ActivityListPalette(ActivityPalette): else: label.set_text(_('Make favorite')) client = gconf.client_get_default() - xo_color = XoColor(client.get_string("/desktop/sugar/user/color")) + xo_color = XoColor(client.get_string('/desktop/sugar/user/color')) self._favorite_icon.props.xo_color = xo_color @@ -452,4 +455,3 @@ class ActivityListPalette(ActivityPalette): def __erase_activate_cb(self, menu_item): self.emit('erase-activated', self._bundle_id) - diff --git a/src/jarabe/desktop/favoriteslayout.py b/src/jarabe/desktop/favoriteslayout.py index 7b847ac..360c147 100644 --- a/src/jarabe/desktop/favoriteslayout.py +++ b/src/jarabe/desktop/favoriteslayout.py @@ -29,6 +29,7 @@ from sugar.graphics import style from jarabe.model import bundleregistry from jarabe.desktop.grid import Grid + _logger = logging.getLogger('FavoritesLayout') _CELL_SIZE = 4 @@ -89,7 +90,8 @@ class FavoritesLayout(gobject.GObject, hippo.CanvasLayout): if icon not in self.box.get_children(): raise ValueError('Child not in box.') - if not(hasattr(icon, 'get_bundle_id') and hasattr(icon, 'get_version')): + if not (hasattr(icon, 'get_bundle_id') and + hasattr(icon, 'get_version')): logging.debug('Not an activity icon %r', icon) return @@ -109,6 +111,7 @@ class FavoritesLayout(gobject.GObject, hippo.CanvasLayout): def allow_dnd(self): return False + class RandomLayout(FavoritesLayout): """Lay out icons randomly; try to nudge them around to resolve overlaps.""" @@ -189,6 +192,7 @@ class RandomLayout(FavoritesLayout): def allow_dnd(self): return True + _MINIMUM_RADIUS = style.XLARGE_ICON_SIZE / 2 + style.DEFAULT_SPACING + \ style.STANDARD_ICON_SIZE * 2 _MAXIMUM_RADIUS = (gtk.gdk.screen_height() - style.GRID_CELL_SIZE) / 2 - \ @@ -239,32 +243,34 @@ class RingLayout(FavoritesLayout): self._spiral_mode = False distance = style.MEDIUM_ICON_SIZE + style.DEFAULT_SPACING * \ _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.MEDIUM_ICON_SIZE)] - radius = max(children_count * distance / (2 * math.pi), _MINIMUM_RADIUS) + radius = max(children_count * distance / (2 * math.pi), + _MINIMUM_RADIUS) if radius < _MAXIMUM_RADIUS: return radius, style.MEDIUM_ICON_SIZE distance = style.STANDARD_ICON_SIZE + style.DEFAULT_SPACING * \ _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.STANDARD_ICON_SIZE)] - radius = max(children_count * distance / (2 * math.pi), _MINIMUM_RADIUS) + radius = max(children_count * distance / (2 * math.pi), + _MINIMUM_RADIUS) if radius < _MAXIMUM_RADIUS: return radius, style.STANDARD_ICON_SIZE self._spiral_mode = True icon_size = style.STANDARD_ICON_SIZE - angle, radius = self._calculate_angle_and_radius(children_count, - icon_size) + angle_, radius = self._calculate_angle_and_radius(children_count, + icon_size) while radius > _MAXIMUM_RADIUS: i = _ICON_SIZES.index(icon_size) if i < len(_ICON_SIZES) - 1: icon_size = _ICON_SIZES[i + 1] - angle, radius = self._calculate_angle_and_radius( + angle_, radius = self._calculate_angle_and_radius( children_count, icon_size) else: break return radius, icon_size - def _calculate_position(self, radius, icon_size, icon_index, children_count, - sin=math.sin, cos=math.cos): + def _calculate_position(self, radius, icon_size, icon_index, + children_count, sin=math.sin, cos=math.cos): """ Calculate an icon position on a circle or a spiral. """ width, height = self.box.get_allocation() if self._spiral_mode: @@ -298,7 +304,7 @@ class RingLayout(FavoritesLayout): _ICON_SPACING_FACTORS[_ICON_SIZES.index(icon_size)] angle = _INITIAL_ANGLE radius = _MINIMUM_RADIUS - (icon_size * _MIMIMUM_RADIUS_ENCROACHMENT) - for i in range(icon_count): + for i_ in range(icon_count): circumference = radius * 2 * math.pi n = circumference / icon_spacing angle += (2 * math.pi / n) @@ -351,6 +357,7 @@ class RingLayout(FavoritesLayout): else: return 0 + _SUNFLOWER_CONSTANT = style.STANDARD_ICON_SIZE * .75 """Chose a constant such that STANDARD_ICON_SIZE icons are nicely spaced.""" @@ -376,6 +383,7 @@ This is the golden angle: http://en.wikipedia.org/wiki/Golden_angle Calculation: math.radians(360) / ( _GOLDEN_RATIO * _GOLDEN_RATIO ) """ + class SunflowerLayout(RingLayout): """Spiral layout based on Fibonacci ratio in phyllotaxis. @@ -403,7 +411,8 @@ class SunflowerLayout(RingLayout): return None, style.STANDARD_ICON_SIZE def adjust_index(self, i): - """Skip floret indices which end up outside the desired bounding box.""" + """Skip floret indices which end up outside the desired bounding box. + """ for idx in self.skipped_indices: if i < idx: break @@ -434,7 +443,7 @@ class SunflowerLayout(RingLayout): # removed to make room for the "active activity" icon. x = r * cos(phi) + (width - icon_size) / 2 y = r * sin(phi) + (height - icon_size - \ - (style.GRID_CELL_SIZE / 2) ) / 2 + (style.GRID_CELL_SIZE / 2)) / 2 # skip allocations outside the allocation box. # give up once we can't fit @@ -442,10 +451,12 @@ class SunflowerLayout(RingLayout): if y < 0 or y > (height - icon_size) or \ x < 0 or x > (width - icon_size): self.skipped_indices.append(index) - continue # try again + # try again + continue return x, y + class BoxLayout(RingLayout): """Lay out icons in a square around the XO man.""" @@ -478,14 +489,16 @@ class BoxLayout(RingLayout): return (90 - d) / 45. if d < 225: return -1 - return cos_d(360 - d) # mirror around 180 + # mirror around 180 + return cos_d(360 - d) cos = lambda r: cos_d(math.degrees(r)) sin = lambda r: cos_d(math.degrees(r) - 90) - return RingLayout._calculate_position\ - (self, radius, icon_size, index, children_count, - sin=sin, cos=cos) + return RingLayout._calculate_position(self, radius, icon_size, index, + children_count, sin=sin, + cos=cos) + class TriangleLayout(RingLayout): """Lay out icons in a triangle around the XO man.""" @@ -524,7 +537,8 @@ class TriangleLayout(RingLayout): return (d + 90) / 120. if d <= 90: return (90 - d) / 60. - return -cos_d(180 - d) # mirror around 90 + # mirror around 90 + return -cos_d(180 - d) sqrt_3 = math.sqrt(3) @@ -535,11 +549,12 @@ class TriangleLayout(RingLayout): return ((d + 90) / 120.) * sqrt_3 - 1 if d <= 90: return sqrt_3 - 1 - return sin_d(180 - d) # mirror around 90 + # mirror around 90 + return sin_d(180 - d) cos = lambda r: cos_d(math.degrees(r)) sin = lambda r: sin_d(math.degrees(r)) - return RingLayout._calculate_position\ - (self, radius, icon_size, index, children_count, - sin=sin, cos=cos) + return RingLayout._calculate_position(self, radius, icon_size, index, + children_count, sin=sin, + cos=cos) diff --git a/src/jarabe/desktop/favoritesview.py b/src/jarabe/desktop/favoritesview.py index ac847e2..b4a4e75 100644 --- a/src/jarabe/desktop/favoritesview.py +++ b/src/jarabe/desktop/favoritesview.py @@ -30,7 +30,6 @@ from sugar.graphics.menuitem import MenuItem from sugar.graphics.alert import Alert from sugar.graphics.xocolor import XoColor from sugar.activity import activityfactory -from sugar.activity.activityhandle import ActivityHandle from sugar import dispatch from sugar.datastore import datastore @@ -38,7 +37,6 @@ from jarabe.view.palettes import JournalPalette from jarabe.view.palettes import CurrentActivityPalette, ActivityPalette from jarabe.view.buddyicon import BuddyIcon from jarabe.view.buddymenu import BuddyMenu -from jarabe.view import launcher from jarabe.model.buddy import get_owner_instance from jarabe.model import shell from jarabe.model import bundleregistry @@ -48,6 +46,7 @@ from jarabe.desktop import schoolserver from jarabe.desktop.schoolserver import RegisterError from jarabe.desktop import favoriteslayout + _logger = logging.getLogger('FavoritesView') _ICON_DND_TARGET = ('activity-icon', gtk.TARGET_SAME_WIDGET, 0) @@ -61,6 +60,9 @@ LAYOUT_MAP = {favoriteslayout.RingLayout.key: favoriteslayout.RingLayout, `FavoritesLayout` which implement the layouts. Additional information about the layout can be accessed with fields of the class.""" +_favorites_settings = None + + class FavoritesView(hippo.Canvas): __gtype_name__ = 'SugarFavoritesView' @@ -171,7 +173,8 @@ class FavoritesView(hippo.Canvas): height = allocation.height min_w_, my_icon_width = self._my_icon.get_width_request() - min_h_, my_icon_height = self._my_icon.get_height_request(my_icon_width) + min_h_, my_icon_height = self._my_icon.get_height_request( + my_icon_width) x = (width - my_icon_width) / 2 y = (height - my_icon_height - style.GRID_CELL_SIZE) / 2 self._layout.move_icon(self._my_icon, x, y, locked=True) @@ -189,7 +192,8 @@ class FavoritesView(hippo.Canvas): # TODO: Dnd methods. This should be merged somehow inside hippo-canvas. def __button_press_event_cb(self, widget, event): if event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS: - self._last_clicked_icon = self._get_icon_at_coords(event.x, event.y) + self._last_clicked_icon = self._get_icon_at_coords(event.x, + event.y) if self._last_clicked_icon is not None: self._pressed_button = event.button self._press_start_x = event.x @@ -202,9 +206,9 @@ class FavoritesView(hippo.Canvas): icon_x, icon_y = icon.get_context().translate_to_widget(icon) icon_width, icon_height = icon.get_allocation() - if (x >= icon_x ) and (x <= icon_x + icon_width) and \ - (y >= icon_y ) and (y <= icon_y + icon_height) and \ - isinstance(icon, ActivityIcon): + if (x >= icon_x) and (x <= icon_x + icon_width) and \ + (y >= icon_y) and (y <= icon_y + icon_height) and \ + isinstance(icon, ActivityIcon): return icon return None @@ -273,7 +277,7 @@ class FavoritesView(hippo.Canvas): def _set_layout(self, layout): if layout not in LAYOUT_MAP: - logging.warn('Unknown favorites layout: %r' % layout) + logging.warn('Unknown favorites layout: %r', layout) layout = favoriteslayout.RingLayout.key assert layout in LAYOUT_MAP @@ -326,7 +330,7 @@ class FavoritesView(hippo.Canvas): alert.props.title = _('Registration Successful') alert.props.msg = _('You are now registered ' \ 'with your school server.') - self._my_icon.remove_register_menu() + self._my_icon.set_registered() ok_icon = Icon(icon_name='dialog-ok') alert.add_button(gtk.RESPONSE_OK, _('Ok'), ok_icon) @@ -386,7 +390,7 @@ class ActivityIcon(CanvasIcon): break def _get_last_activity_async(self, bundle_id, properties): - query = {'activity': bundle_id} + query = {'activity': bundle_id} datastore.find(query, sorting=['+timestamp'], limit=self._MAX_RESUME_ENTRIES, properties=properties, @@ -394,8 +398,8 @@ class ActivityIcon(CanvasIcon): error_handler=self.__get_last_activity_error_handler_cb) def __get_last_activity_reply_handler_cb(self, entries, total_count): - # If there's a problem with the DS index, we may get entries not related - # to this activity. + # If there's a problem with the DS index, we may get entries not + # related to this activity. checked_entries = [] for entry in entries: if entry['activity'] == self.bundle_id: @@ -506,6 +510,7 @@ class ActivityIcon(CanvasIcon): self._resume_mode = resume_mode self._update() + class FavoritePalette(ActivityPalette): __gtype_name__ = 'SugarFavoritePalette' @@ -554,6 +559,7 @@ class FavoritePalette(ActivityPalette): if entry is not None: self.emit('entry-activate', entry) + class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem): def __init__(self): CanvasIcon.__init__(self, cache=True) @@ -592,13 +598,15 @@ class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem): self._home_activity = home_activity self._update() + class OwnerIcon(BuddyIcon): __gtype_name__ = 'SugarFavoritesOwnerIcon' __gsignals__ = { - 'register-activate' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])) + 'register-activate': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([])), } + def __init__(self, size): BuddyIcon.__init__(self, buddy=get_owner_instance(), size=size) @@ -614,11 +622,16 @@ class OwnerIcon(BuddyIcon): client = gconf.client_get_default() backup_url = client.get_string('/desktop/sugar/backup_url') + if not backup_url: self._register_menu = MenuItem(_('Register'), 'media-record') - self._register_menu.connect('activate', self.__register_activate_cb) - palette.menu.append(self._register_menu) - self._register_menu.show() + else: + self._register_menu = MenuItem(_('Register again'), + 'media-record') + + self._register_menu.connect('activate', self.__register_activate_cb) + palette.menu.append(self._register_menu) + self._register_menu.show() return palette @@ -628,12 +641,17 @@ class OwnerIcon(BuddyIcon): def __register_activate_cb(self, menuitem): self.emit('register-activate') - def remove_register_menu(self): + def set_registered(self): self.palette.menu.remove(self._register_menu) + self._register_menu = MenuItem(_('Register again'), 'media-record') + self._register_menu.connect('activate', self.__register_activate_cb) + self.palette.menu.append(self._register_menu) + self._register_menu.show() + class FavoritesSetting(object): - _FAVORITES_KEY = "/desktop/sugar/desktop/favorites_layout" + _FAVORITES_KEY = '/desktop/sugar/desktop/favorites_layout' def __init__(self): client = gconf.client_get_default() @@ -659,7 +677,6 @@ class FavoritesSetting(object): layout = property(get_layout, set_layout) -_favorites_settings = None def get_settings(): global _favorites_settings diff --git a/src/jarabe/desktop/friendview.py b/src/jarabe/desktop/friendview.py index 53b8f5e..8dab35f 100644 --- a/src/jarabe/desktop/friendview.py +++ b/src/jarabe/desktop/friendview.py @@ -23,6 +23,7 @@ from sugar.graphics import style from jarabe.view.buddyicon import BuddyIcon from jarabe.model import bundleregistry + class FriendView(hippo.CanvasBox): def __init__(self, buddy, **kwargs): hippo.CanvasBox.__init__(self, **kwargs) @@ -81,4 +82,3 @@ class FriendView(hippo.CanvasBox): def __buddy_notify_color_cb(self, buddy, pspec): # TODO: shouldn't this change self._buddy_icon instead? self._activity_icon.props.xo_color = buddy.props.color - diff --git a/src/jarabe/desktop/grid.py b/src/jarabe/desktop/grid.py index f3412c9..eab4033 100644 --- a/src/jarabe/desktop/grid.py +++ b/src/jarabe/desktop/grid.py @@ -22,17 +22,19 @@ import gtk from sugar import _sugarext + _PLACE_TRIALS = 20 _MAX_WEIGHT = 255 _REFRESH_RATE = 200 _MAX_COLLISIONS_PER_REFRESH = 20 + class Grid(_sugarext.Grid): __gsignals__ = { - 'child-changed' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) + 'child-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), } + def __init__(self, width, height): gobject.GObject.__init__(self) @@ -185,7 +187,8 @@ class Grid(_sugarext.Grid): for c in self._children: intersection = child_rect.intersect(self._child_rects[c]) if c != child and intersection.width > 0: - if c not in self._locked_children and c not in self._collisions: + if (c not in self._locked_children and + c not in self._collisions): collision_found = True self._collisions.append(c) diff --git a/src/jarabe/desktop/groupbox.py b/src/jarabe/desktop/groupbox.py index 89043fe..ed8f8ae 100644 --- a/src/jarabe/desktop/groupbox.py +++ b/src/jarabe/desktop/groupbox.py @@ -30,10 +30,12 @@ from jarabe.model import friends from jarabe.desktop.friendview import FriendView from jarabe.desktop.spreadlayout import SpreadLayout + class GroupBox(hippo.Canvas): __gtype_name__ = 'SugarGroupBox' + def __init__(self): - logging.debug("STARTUP: Loading the group view") + logging.debug('STARTUP: Loading the group view') gobject.GObject.__init__(self) @@ -47,7 +49,7 @@ class GroupBox(hippo.Canvas): self._box.set_layout(self._layout) client = gconf.client_get_default() - color = XoColor(client.get_string("/desktop/sugar/user/color")) + color = XoColor(client.get_string('/desktop/sugar/user/color')) self._owner_icon = CanvasIcon(icon_name='computer-xo', cache=True, xo_color=color) diff --git a/src/jarabe/desktop/homebox.py b/src/jarabe/desktop/homebox.py index 85279ff..661326e 100644 --- a/src/jarabe/desktop/homebox.py +++ b/src/jarabe/desktop/homebox.py @@ -30,16 +30,18 @@ from sugar.graphics.icon import Icon from jarabe.desktop import favoritesview from jarabe.desktop.activitieslist import ActivitiesList + _FAVORITES_VIEW = 0 _LIST_VIEW = 1 _AUTOSEARCH_TIMEOUT = 1000 + class HomeBox(gtk.VBox): __gtype_name__ = 'SugarHomeBox' def __init__(self): - logging.debug("STARTUP: Loading the home view") + logging.debug('STARTUP: Loading the home view') gobject.GObject.__init__(self) @@ -57,7 +59,7 @@ class HomeBox(gtk.VBox): def show_software_updates_alert(self): alert = Alert() updater_icon = Icon(icon_name='module-updater', - pixel_size = style.STANDARD_ICON_SIZE) + pixel_size=style.STANDARD_ICON_SIZE) alert.props.icon = updater_icon updater_icon.show() alert.props.title = _('Software Update') @@ -125,7 +127,7 @@ class HomeBox(gtk.VBox): else: raise ValueError('Invalid view: %r' % view) - _REDRAW_TIMEOUT = 5 * 60 * 1000 # 5 minutes + _REDRAW_TIMEOUT = 5 * 60 * 1000 # 5 minutes def resume(self): pass @@ -144,16 +146,15 @@ class HomeBox(gtk.VBox): def set_resume_mode(self, resume_mode): self._favorites_view.set_resume_mode(resume_mode) + class HomeToolbar(gtk.Toolbar): __gtype_name__ = 'SugarHomeToolbar' __gsignals__ = { - 'query-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, + 'query-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])), - 'view-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([object])) + 'view-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), } def __init__(self): @@ -239,13 +240,14 @@ class HomeToolbar(gtk.Toolbar): if self._autosearch_timer: gobject.source_remove(self._autosearch_timer) self._autosearch_timer = gobject.timeout_add(_AUTOSEARCH_TIMEOUT, - self.__autosearch_timer_cb) + self.__autosearch_timer_cb) def __autosearch_timer_cb(self): self._autosearch_timer = None self.search_entry.activate() return False + class FavoritesButton(RadioToolButton): __gtype_name__ = 'SugarFavoritesButton' @@ -295,4 +297,3 @@ class FavoritesButton(RadioToolButton): def _update_icon(self): self.props.named_icon = favoritesview.LAYOUT_MAP[self._layout]\ .icon_name - diff --git a/src/jarabe/desktop/homewindow.py b/src/jarabe/desktop/homewindow.py index fec4289..07deff7 100644 --- a/src/jarabe/desktop/homewindow.py +++ b/src/jarabe/desktop/homewindow.py @@ -16,6 +16,7 @@ import logging +import gobject import gtk from sugar.graphics import style @@ -28,11 +29,15 @@ from jarabe.desktop.transitionbox import TransitionBox from jarabe.model.shell import ShellModel from jarabe.model import shell -_HOME_PAGE = 0 -_GROUP_PAGE = 1 -_MESH_PAGE = 2 + +_HOME_PAGE = 0 +_GROUP_PAGE = 1 +_MESH_PAGE = 2 _TRANSITION_PAGE = 3 +_instance = None + + class HomeWindow(gtk.Window): def __init__(self): logging.debug('STARTUP: Loading the desktop window') @@ -75,7 +80,7 @@ class HomeWindow(gtk.Window): self.__zoom_level_changed_cb) def _deactivate_view(self, level): - group = palettegroup.get_group("default") + group = palettegroup.get_group('default') group.popdown() if level == ShellModel.ZOOM_HOME: self._home_box.suspend() @@ -183,11 +188,22 @@ class HomeWindow(gtk.Window): def get_home_box(self): return self._home_box -_instance = None + def busy_during_delayed_action(self, action): + """Use busy cursor during execution of action, scheduled via idle_add. + """ + def action_wrapper(old_cursor): + try: + action() + finally: + self.get_window().set_cursor(old_cursor) + + old_cursor = self.get_window().get_cursor() + self.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + gobject.idle_add(action_wrapper, old_cursor) + def get_instance(): global _instance if not _instance: _instance = HomeWindow() return _instance - diff --git a/src/jarabe/desktop/keydialog.py b/src/jarabe/desktop/keydialog.py index 1e6d17a..6241b9b 100644 --- a/src/jarabe/desktop/keydialog.py +++ b/src/jarabe/desktop/keydialog.py @@ -24,8 +24,14 @@ import dbus from jarabe.model import network from jarabe.model.network import Secrets + IW_AUTH_ALG_OPEN_SYSTEM = 'open' -IW_AUTH_ALG_SHARED_KEY = 'shared' +IW_AUTH_ALG_SHARED_KEY = 'shared' + +WEP_PASSPHRASE = 1 +WEP_HEX = 2 +WEP_ASCII = 3 + def string_is_hex(key): is_hex = True @@ -34,6 +40,7 @@ def string_is_hex(key): is_hex = False return is_hex + def string_is_ascii(string): try: string.encode('ascii') @@ -41,12 +48,14 @@ def string_is_ascii(string): except UnicodeEncodeError: return False + def string_to_hex(passphrase): key = '' for c in passphrase: key += '%02x' % ord(c) return key + def hash_passphrase(passphrase): # passphrase must have a length of 64 if len(passphrase) > 64: @@ -57,16 +66,18 @@ def hash_passphrase(passphrase): passphrase = hashlib.md5(passphrase).digest() return string_to_hex(passphrase)[:26] + class CanceledKeyRequestError(dbus.DBusException): def __init__(self): dbus.DBusException.__init__(self) self._dbus_error_name = network.NM_SETTINGS_IFACE + '.CanceledError' + class KeyDialog(gtk.Dialog): def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, response): gtk.Dialog.__init__(self, flags=gtk.DIALOG_MODAL) - self.set_title("Wireless Key Required") + self.set_title('Wireless Key Required') self._settings = settings self._response = response @@ -108,9 +119,6 @@ class KeyDialog(gtk.Dialog): def get_response_object(self): return self._response -WEP_PASSPHRASE = 1 -WEP_HEX = 2 -WEP_ASCII = 3 class WEPKeyDialog(KeyDialog): def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, @@ -120,9 +128,9 @@ class WEPKeyDialog(KeyDialog): # WEP key type self.key_store = gtk.ListStore(str, int) - self.key_store.append(["Passphrase (128-bit)", WEP_PASSPHRASE]) - self.key_store.append(["Hex (40/128-bit)", WEP_HEX]) - self.key_store.append(["ASCII (40/128-bit)", WEP_ASCII]) + self.key_store.append(['Passphrase (128-bit)', WEP_PASSPHRASE]) + self.key_store.append(['Hex (40/128-bit)', WEP_HEX]) + self.key_store.append(['ASCII (40/128-bit)', WEP_ASCII]) self.key_combo = gtk.ComboBox(self.key_store) cell = gtk.CellRendererText() @@ -132,7 +140,7 @@ class WEPKeyDialog(KeyDialog): self.key_combo.connect('changed', self._key_combo_changed_cb) hbox = gtk.HBox() - hbox.pack_start(gtk.Label(_("Key Type:"))) + hbox.pack_start(gtk.Label(_('Key Type:'))) hbox.pack_start(self.key_combo) hbox.show_all() self.vbox.pack_start(hbox) @@ -142,8 +150,8 @@ class WEPKeyDialog(KeyDialog): # WEP authentication mode self.auth_store = gtk.ListStore(str, str) - self.auth_store.append(["Open System", IW_AUTH_ALG_OPEN_SYSTEM]) - self.auth_store.append(["Shared Key", IW_AUTH_ALG_SHARED_KEY]) + self.auth_store.append(['Open System', IW_AUTH_ALG_OPEN_SYSTEM]) + self.auth_store.append(['Shared Key', IW_AUTH_ALG_SHARED_KEY]) self.auth_combo = gtk.ComboBox(self.auth_store) cell = gtk.CellRendererText() @@ -152,7 +160,7 @@ class WEPKeyDialog(KeyDialog): self.auth_combo.set_active(0) hbox = gtk.HBox() - hbox.pack_start(gtk.Label(_("Authentication Type:"))) + hbox.pack_start(gtk.Label(_('Authentication Type:'))) hbox.pack_start(self.auth_combo) hbox.show_all() @@ -179,8 +187,8 @@ class WEPKeyDialog(KeyDialog): def print_security(self): (key, auth_alg) = self._get_security() - print "Key: %s" % key - print "Auth: %d" % auth_alg + print 'Key: %s' % key + print 'Auth: %d' % auth_alg def create_security(self): (key, auth_alg) = self._get_security() @@ -209,6 +217,7 @@ class WEPKeyDialog(KeyDialog): self.set_response_sensitive(gtk.RESPONSE_OK, valid) + class WPAKeyDialog(KeyDialog): def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, response): @@ -217,7 +226,7 @@ class WPAKeyDialog(KeyDialog): self.add_key_entry() self.store = gtk.ListStore(str) - self.store.append([_("WPA & WPA2 Personal")]) + self.store.append([_('WPA & WPA2 Personal')]) self.combo = gtk.ComboBox(self.store) cell = gtk.CellRendererText() @@ -226,7 +235,7 @@ class WPAKeyDialog(KeyDialog): self.combo.set_active(0) self.hbox = gtk.HBox() - self.hbox.pack_start(gtk.Label(_("Wireless Security:"))) + self.hbox.pack_start(gtk.Label(_('Wireless Security:'))) self.hbox.pack_start(self.combo) self.hbox.show_all() @@ -246,21 +255,21 @@ class WPAKeyDialog(KeyDialog): from subprocess import Popen, PIPE p = Popen(['wpa_passphrase', ssid, key], stdout=PIPE) for line in p.stdout: - if line.strip().startswith("psk="): + if line.strip().startswith('psk='): real_key = line.strip()[4:] if p.wait() != 0: - raise RuntimeError("Error hashing passphrase") + raise RuntimeError('Error hashing passphrase') if real_key and len(real_key) != 64: real_key = None if not real_key: - raise RuntimeError("Invalid key") + raise RuntimeError('Invalid key') return real_key def print_security(self): key = self._get_security() - print "Key: %s" % key + print 'Key: %s' % key def create_security(self): secrets = Secrets(self._settings) @@ -281,6 +290,7 @@ class WPAKeyDialog(KeyDialog): self.set_response_sensitive(gtk.RESPONSE_OK, valid) return False + def create(ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, response): if wpa_flags == network.NM_802_11_AP_SEC_NONE and \ rsn_flags == network.NM_802_11_AP_SEC_NONE: @@ -290,13 +300,15 @@ def create(ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, response): key_dialog = WPAKeyDialog(ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, response) - key_dialog.connect("response", _key_dialog_response_cb) - key_dialog.connect("destroy", _key_dialog_destroy_cb) + key_dialog.connect('response', _key_dialog_response_cb) + key_dialog.connect('destroy', _key_dialog_destroy_cb) key_dialog.show_all() + def _key_dialog_destroy_cb(key_dialog, data=None): _key_dialog_response_cb(key_dialog, gtk.RESPONSE_CANCEL) + def _key_dialog_response_cb(key_dialog, response_id): response = key_dialog.get_response_object() secrets = None @@ -308,10 +320,9 @@ def _key_dialog_response_cb(key_dialog, response_id): response.set_error(CanceledKeyRequestError()) elif response_id == gtk.RESPONSE_OK: if not secrets: - raise RuntimeError("Invalid security arguments.") + raise RuntimeError('Invalid security arguments.') response.set_secrets(secrets) else: - raise RuntimeError("Unhandled key dialog response %d" % response_id) + raise RuntimeError('Unhandled key dialog response %d' % response_id) key_dialog.destroy() - diff --git a/src/jarabe/desktop/meshbox.py b/src/jarabe/desktop/meshbox.py index 036f00a..ad4b873 100644 --- a/src/jarabe/desktop/meshbox.py +++ b/src/jarabe/desktop/meshbox.py @@ -47,6 +47,7 @@ from jarabe.model.olpcmesh import OlpcMeshManager from jarabe.model.adhoc import get_adhoc_manager_instance from jarabe.journal import misc + _NM_SERVICE = 'org.freedesktop.NetworkManager' _NM_IFACE = 'org.freedesktop.NetworkManager' _NM_PATH = '/org/freedesktop/NetworkManager' @@ -59,6 +60,9 @@ _NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' _AP_ICON_NAME = 'network-wireless' _OLPC_MESH_ICON_NAME = 'network-mesh' +_AUTOSEARCH_TIMEOUT = 1000 + + class ActivityView(hippo.CanvasBox): def __init__(self, model): hippo.CanvasBox.__init__(self) @@ -115,7 +119,7 @@ class ActivityView(hippo.CanvasBox): return p def has_buddy_icon(self, key): - return self._icons.has_key(key) + return key in self._icons def __buddy_added_cb(self, activity, buddy): self._add_buddy(buddy) @@ -148,16 +152,13 @@ class ActivityView(hippo.CanvasBox): if hasattr(icon, 'set_filter'): icon.set_filter(query) -_AUTOSEARCH_TIMEOUT = 1000 - class MeshToolbar(gtk.Toolbar): __gtype_name__ = 'MeshToolbar' __gsignals__ = { - 'query-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([str])) + 'query-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str])), } def __init__(self): @@ -225,16 +226,18 @@ class DeviceObserver(gobject.GObject): 'access-point-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), 'access-point-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) + ([gobject.TYPE_PYOBJECT])), } + def __init__(self, device): gobject.GObject.__init__(self) self._bus = dbus.SystemBus() self.device = device wireless = dbus.Interface(device, _NM_WIRELESS_IFACE) - wireless.GetAccessPoints(reply_handler=self._get_access_points_reply_cb, - error_handler=self._get_access_points_error_cb) + wireless.GetAccessPoints( + reply_handler=self._get_access_points_reply_cb, + error_handler=self._get_access_points_error_cb) self._bus.add_signal_receiver(self.__access_point_added_cb, signal_name='AccessPointAdded', @@ -315,13 +318,12 @@ class NetworkManagerObserver(object): # FIXME It would be better to do all of this async, but I cannot think # of a good way to. NM could really use some love here. - netmgr_props = dbus.Interface( - self._netmgr, 'org.freedesktop.DBus.Properties') + netmgr_props = dbus.Interface(self._netmgr, dbus.PROPERTIES_IFACE) active_connections_o = netmgr_props.Get(_NM_IFACE, 'ActiveConnections') for conn_o in active_connections_o: obj = self._bus.get_object(_NM_IFACE, conn_o) - props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') + props = dbus.Interface(obj, dbus.PROPERTIES_IFACE) state = props.Get(_NM_ACTIVE_CONN_IFACE, 'State') if state == network.NM_ACTIVE_CONNECTION_STATE_ACTIVATING: ap_o = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject') @@ -333,8 +335,8 @@ class NetworkManagerObserver(object): settings = kwargs['connection'].get_settings() net.create_keydialog(settings, kwargs['response']) if not found: - logging.error('Could not determine AP for' - ' specific object %s' % conn_o) + logging.error('Could not determine AP for specific object' + ' %s', conn_o) def __get_devices_reply_cb(self, devices_o): for dev_o in devices_o: @@ -345,7 +347,7 @@ class NetworkManagerObserver(object): def _check_device(self, device_o): device = self._bus.get_object(_NM_SERVICE, device_o) - props = dbus.Interface(device, 'org.freedesktop.DBus.Properties') + props = dbus.Interface(device, dbus.PROPERTIES_IFACE) device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType') if device_type == network.DEVICE_TYPE_802_11_WIRELESS: @@ -399,7 +401,7 @@ class MeshBox(gtk.VBox): __gtype_name__ = 'SugarMeshBox' def __init__(self): - logging.debug("STARTUP: Loading the mesh view") + logging.debug('STARTUP: Loading the mesh view') gobject.GObject.__init__(self) @@ -520,56 +522,59 @@ class MeshBox(gtk.VBox): # add AP to its corresponding network icon on the desktop, # creating one if it doesn't already exist def _add_ap_to_network(self, ap): - hash = ap.network_hash() - if hash in self.wireless_networks: - self.wireless_networks[hash].add_ap(ap) + hash_value = ap.network_hash() + if hash_value in self.wireless_networks: + self.wireless_networks[hash_value].add_ap(ap) else: # this is a new network icon = WirelessNetworkView(ap) - self.wireless_networks[hash] = icon + self.wireless_networks[hash_value] = icon self._layout.add(icon) if hasattr(icon, 'set_filter'): icon.set_filter(self._query) - def _remove_net_if_empty(self, net, hash): + def _remove_net_if_empty(self, net, hash_value): # remove a network if it has no APs left if net.num_aps() == 0: net.disconnect() self._layout.remove(net) - del self.wireless_networks[hash] + del self.wireless_networks[hash_value] - def _ap_props_changed_cb(self, ap, old_hash): + def _ap_props_changed_cb(self, ap, old_hash_value): # if we have mesh hardware, ignore OLPC mesh networks that appear as # normal wifi networks if len(self._mesh) > 0 and ap.mode == network.NM_802_11_MODE_ADHOC \ - and ap.name == "olpc-mesh": - logging.debug("ignoring OLPC mesh IBSS") + and ap.name == 'olpc-mesh': + logging.debug('ignoring OLPC mesh IBSS') ap.disconnect() return if self._adhoc_manager is not None and \ network.is_sugar_adhoc_network(ap.name) and \ ap.mode == network.NM_802_11_MODE_ADHOC: - if old_hash is None: # new Ad-hoc network finished initializing + if old_hash_value is None: + # new Ad-hoc network finished initializing self._adhoc_manager.add_access_point(ap) # we are called as well in other cases but we do not need to # act here as we don't display signal strength for Ad-hoc networks return - if old_hash is None: # new AP finished initializing + if old_hash_value is None: + # new AP finished initializing self._add_ap_to_network(ap) return - hash = ap.network_hash() - if old_hash == hash: + hash_value = ap.network_hash() + if old_hash_value == hash_value: # no change in network identity, so just update signal strengths - self.wireless_networks[hash].update_strength() + self.wireless_networks[hash_value].update_strength() return # properties change includes a change of the identity of the network # that it is on. so create this as a new network. - self.wireless_networks[old_hash].remove_ap(ap) - self._remove_net_if_empty(self.wireless_networks[old_hash], old_hash) + self.wireless_networks[old_hash_value].remove_ap(ap) + self._remove_net_if_empty(self.wireless_networks[old_hash_value], + old_hash_value) self._add_ap_to_network(ap) def add_access_point(self, device, ap_o): @@ -597,7 +602,7 @@ class MeshBox(gtk.VBox): # it's not an error if the AP isn't found, since we might have ignored # it (e.g. olpc-mesh adhoc network) - logging.debug('Can not remove access point %s' % ap_o) + logging.debug('Can not remove access point %s', ap_o) def add_adhoc_networks(self, device): if self._adhoc_manager is None: @@ -631,15 +636,15 @@ class MeshBox(gtk.VBox): # the OLPC mesh can be recognised as a "normal" wifi network. remove # any such normal networks if they have been created - for hash, net in self.wireless_networks.iteritems(): + for hash_value, net in self.wireless_networks.iteritems(): if not net.is_olpc_mesh(): continue - logging.debug("removing OLPC mesh IBSS") + logging.debug('removing OLPC mesh IBSS') net.remove_all_aps() net.disconnect() self._layout.remove(net) - del self.wireless_networks[hash] + del self.wireless_networks[hash_value] def disable_olpc_mesh(self, mesh_device): for icon in self._mesh: diff --git a/src/jarabe/desktop/networkviews.py b/src/jarabe/desktop/networkviews.py index 87f182f..99d46b6 100644 --- a/src/jarabe/desktop/networkviews.py +++ b/src/jarabe/desktop/networkviews.py @@ -41,6 +41,7 @@ from jarabe.model.network import IP4Config from jarabe.model.network import WirelessSecurity from jarabe.model.adhoc import get_adhoc_manager_instance + _NM_SERVICE = 'org.freedesktop.NetworkManager' _NM_IFACE = 'org.freedesktop.NetworkManager' _NM_PATH = '/org/freedesktop/NetworkManager' @@ -74,7 +75,6 @@ class WirelessNetworkView(CanvasPulsingIcon): self._rsn_flags = initial_ap.rsn_flags self._device_caps = 0 self._device_state = None - self._connection = None self._color = None if self._mode == network.NM_802_11_MODE_ADHOC and \ @@ -100,23 +100,9 @@ class WirelessNetworkView(CanvasPulsingIcon): self._palette = self._create_palette() self.set_palette(self._palette) self._palette_icon.props.xo_color = self._color + self._update_badge() - if self._mode != network.NM_802_11_MODE_ADHOC: - if network.find_connection_by_ssid(self._name) is not None: - self.props.badge_name = "emblem-favorite" - self._palette_icon.props.badge_name = "emblem-favorite" - elif self._flags == network.NM_802_11_AP_FLAGS_PRIVACY: - self.props.badge_name = "emblem-locked" - self._palette_icon.props.badge_name = "emblem-locked" - else: - self.props.badge_name = None - self._palette_icon.props.badge_name = None - else: - self.props.badge_name = None - self._palette_icon.props.badge_name = None - - interface_props = dbus.Interface(self._device, - 'org.freedesktop.DBus.Properties') + interface_props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE) interface_props.Get(_NM_DEVICE_IFACE, 'State', reply_handler=self.__get_device_state_reply_cb, error_handler=self.__get_device_state_error_cb) @@ -160,6 +146,7 @@ class WirelessNetworkView(CanvasPulsingIcon): self._device_state = new_state self._update_state() self._update_icon() + self._update_badge() def __update_active_ap(self, ap_path): if ap_path in self._access_points: @@ -192,6 +179,7 @@ class WirelessNetworkView(CanvasPulsingIcon): self._device_state = state self._update_state() self._update_color() + self._update_badge() def __get_device_state_error_cb(self, err): logging.error('Error getting the device state: %s', err) @@ -222,6 +210,21 @@ class WirelessNetworkView(CanvasPulsingIcon): icon = self._palette.props.icon icon.props.icon_name = icon_name + def _update_badge(self): + if self._mode != network.NM_802_11_MODE_ADHOC: + if network.find_connection_by_ssid(self._name) is not None: + self.props.badge_name = 'emblem-favorite' + self._palette_icon.props.badge_name = 'emblem-favorite' + elif self._flags == network.NM_802_11_AP_FLAGS_PRIVACY: + self.props.badge_name = 'emblem-locked' + self._palette_icon.props.badge_name = 'emblem-locked' + else: + self.props.badge_name = None + self._palette_icon.props.badge_name = None + else: + self.props.badge_name = None + self._palette_icon.props.badge_name = None + def _update_state(self): if self._active_ap is not None: state = self._device_state @@ -262,24 +265,30 @@ class WirelessNetworkView(CanvasPulsingIcon): self.props.base_color = self._color def _disconnect_activate_cb(self, item): - pass + if self._mode == network.NM_802_11_MODE_INFRA: + connection = network.find_connection_by_ssid(self._name) + if connection: + connection.disable_autoconnect() + + ap_paths = self._access_points.keys() + network.disconnect_access_points(ap_paths) def _add_ciphers_from_flags(self, flags, pairwise): ciphers = [] if pairwise: if flags & network.NM_802_11_AP_SEC_PAIR_TKIP: - ciphers.append("tkip") + ciphers.append('tkip') if flags & network.NM_802_11_AP_SEC_PAIR_CCMP: - ciphers.append("ccmp") + ciphers.append('ccmp') else: if flags & network.NM_802_11_AP_SEC_GROUP_WEP40: - ciphers.append("wep40") + ciphers.append('wep40') if flags & network.NM_802_11_AP_SEC_GROUP_WEP104: - ciphers.append("wep104") + ciphers.append('wep104') if flags & network.NM_802_11_AP_SEC_GROUP_TKIP: - ciphers.append("tkip") + ciphers.append('tkip') if flags & network.NM_802_11_AP_SEC_GROUP_CCMP: - ciphers.append("ccmp") + ciphers.append('ccmp') return ciphers def _get_security(self): @@ -363,7 +372,7 @@ class WirelessNetworkView(CanvasPulsingIcon): netmgr.ActivateConnection(network.SETTINGS_SERVICE, connection.path, self._device.object_path, - "/", + '/', reply_handler=self.__activate_reply_cb, error_handler=self.__activate_error_cb) @@ -380,7 +389,8 @@ class WirelessNetworkView(CanvasPulsingIcon): def create_keydialog(self, settings, response): keydialog.create(self._name, self._flags, self._wpa_flags, - self._rsn_flags, self._device_caps, settings, response) + self._rsn_flags, self._device_caps, settings, + response) def update_strength(self): if self._active_ap is not None: @@ -419,7 +429,7 @@ class WirelessNetworkView(CanvasPulsingIcon): def is_olpc_mesh(self): return self._mode == network.NM_802_11_MODE_ADHOC \ - and self.name == "olpc-mesh" + and self.name == 'olpc-mesh' def remove_all_aps(self): for ap in self._access_points.values(): @@ -438,6 +448,7 @@ class WirelessNetworkView(CanvasPulsingIcon): path=self._device.object_path, dbus_interface=_NM_WIRELESS_IFACE) + class SugarAdhocView(CanvasPulsingIcon): """To mimic the mesh behavior on devices where mesh hardware is not available we support the creation of an Ad-hoc network on @@ -483,7 +494,7 @@ class SugarAdhocView(CanvasPulsingIcon): icon_name=self._ICON_NAME + str(self._channel), icon_size=style.STANDARD_ICON_SIZE) - palette_ = palette.Palette(_("Ad-hoc Network %d") % self._channel, + palette_ = palette.Palette(_('Ad-hoc Network %d') % self._channel, icon=self._palette_icon) self._connect_item = MenuItem(_('Connect'), 'dialog-ok') @@ -517,9 +528,6 @@ class SugarAdhocView(CanvasPulsingIcon): else: icon_name = self._ICON_NAME + str(self._channel) - self.props.base_color = self._state_color - self._palette_icon.props.xo_color = self._state_color - if icon_name is not None: self.props.icon_name = icon_name icon = self._palette.props.icon @@ -549,6 +557,7 @@ class SugarAdhocView(CanvasPulsingIcon): def _update_color(self): if self._greyed_out: + self.props.pulsing = False self.props.base_color = XoColor('#D5D5D5,#D5D5D5') else: self.props.base_color = self._state_color @@ -557,12 +566,12 @@ class SugarAdhocView(CanvasPulsingIcon): if channel == self._channel: if has_members == True: self._state_color = profile.get_color() - self.props.base_color = self._state_color - self._palette_icon.props.xo_color = self._state_color else: color = '%s,%s' % (profile.get_color().get_stroke_color(), style.COLOR_TRANSPARENT.get_svg()) self._state_color = XoColor(color) + + if not self._greyed_out: self.props.base_color = self._state_color self._palette_icon.props.xo_color = self._state_color @@ -584,14 +593,12 @@ class OlpcMeshView(CanvasPulsingIcon): self._greyed_out = False self._name = '' self._device_state = None - self._connection = None self._active = False device = mesh_mgr.mesh_device self.connect('button-release-event', self.__button_release_event_cb) - interface_props = dbus.Interface(device, - 'org.freedesktop.DBus.Properties') + interface_props = dbus.Interface(device, dbus.PROPERTIES_IFACE) interface_props.Get(_NM_DEVICE_IFACE, 'State', reply_handler=self.__get_device_state_reply_cb, error_handler=self.__get_device_state_error_cb) @@ -616,7 +623,7 @@ class OlpcMeshView(CanvasPulsingIcon): self.set_palette(self._palette) def _create_palette(self): - _palette = palette.Palette(_("Mesh Network %d") % self._channel) + _palette = palette.Palette(_('Mesh Network %d') % self._channel) self._connect_item = MenuItem(_('Connect'), 'dialog-ok') self._connect_item.connect('activate', self.__connect_activate_cb) @@ -712,4 +719,3 @@ class OlpcMeshView(CanvasPulsingIcon): signal_name='PropertiesChanged', path=device_object_path, dbus_interface=_NM_OLPC_MESH_IFACE) - diff --git a/src/jarabe/desktop/schoolserver.py b/src/jarabe/desktop/schoolserver.py index a05f56c..aea2357 100644 --- a/src/jarabe/desktop/schoolserver.py +++ b/src/jarabe/desktop/schoolserver.py @@ -16,8 +16,9 @@ import logging from gettext import gettext as _ -from xmlrpclib import ServerProxy, Error +import xmlrpclib import socket +import httplib import os from string import ascii_uppercase import random @@ -29,15 +30,17 @@ import gconf from sugar import env from sugar.profile import get_profile -REGISTER_URL = 'http://schoolserver:8080/' +_REGISTER_URL = 'http://schoolserver:8080/' +_REGISTER_TIMEOUT = 8 -def generate_serial_number(): + +def _generate_serial_number(): """ Generates a serial number based on 3 random uppercase letters and the last 8 digits of the current unix seconds. """ serial_part1 = [] - for y_ in range(3) : + for y_ in range(3): serial_part1.append(random.choice(ascii_uppercase)) serial_part1 = ''.join(serial_part1) @@ -46,7 +49,8 @@ def generate_serial_number(): return serial -def store_identifiers(serial_number, uuid, backup_url): + +def _store_identifiers(serial_number, uuid_, backup_url): """ Stores the serial number, uuid and backup_url in the identifier folder inside the profile directory so that these identifiers can be used for backup. """ @@ -64,7 +68,7 @@ def store_identifiers(serial_number, uuid, backup_url): if os.path.exists(os.path.join(identifier_path, 'uuid')): os.remove(os.path.join(identifier_path, 'uuid')) uuid_file = open(os.path.join(identifier_path, 'uuid'), 'w') - uuid_file.write(uuid) + uuid_file.write(uuid_) uuid_file.close() if os.path.exists(os.path.join(identifier_path, 'backup_url')): @@ -73,33 +77,56 @@ def store_identifiers(serial_number, uuid, backup_url): backup_url_file.write(backup_url) backup_url_file.close() + class RegisterError(Exception): pass -def register_laptop(url=REGISTER_URL): + +class _TimeoutHTTP(httplib.HTTP): + + def __init__(self, host='', port=None, strict=None, timeout=None): + if port == 0: + port = None + # FIXME: Depending on undocumented internals that can break between + # Python releases. Please have a look at SL #2350 + self._setup(self._connection_class(host, + port, strict, timeout=_REGISTER_TIMEOUT)) + + +class _TimeoutTransport(xmlrpclib.Transport): + + def make_connection(self, host): + host, extra_headers, x509_ = self.get_host_info(host) + return _TimeoutHTTP(host, timeout=_REGISTER_TIMEOUT) + + +def register_laptop(url=_REGISTER_URL): profile = get_profile() client = gconf.client_get_default() - if have_ofw_tree(): - sn = read_ofw('mfg-data/SN') - uuid_ = read_ofw('mfg-data/U#') + if _have_ofw_tree(): + sn = _read_ofw('mfg-data/SN') + uuid_ = _read_ofw('mfg-data/U#') sn = sn or 'SHF00000000' uuid_ = uuid_ or '00000000-0000-0000-0000-000000000000' else: - sn = generate_serial_number() + sn = _generate_serial_number() uuid_ = str(uuid.uuid1()) - setting_name = '/desktop/sugar/collaboration/jabber_server' - jabber_server = client.get_string(setting_name) - store_identifiers(sn, uuid_, jabber_server) + + setting_name = '/desktop/sugar/collaboration/jabber_server' + jabber_server = client.get_string(setting_name) + _store_identifiers(sn, uuid_, jabber_server) + + if jabber_server: url = 'http://' + jabber_server + ':8080/' nick = client.get_string('/desktop/sugar/user/nick') - server = ServerProxy(url) + server = xmlrpclib.ServerProxy(url, _TimeoutTransport()) try: data = server.register(sn, nick, uuid_, profile.pubkey) - except (Error, TypeError, socket.error): + except (xmlrpclib.Error, TypeError, socket.error): logging.exception('Registration: cannot connect to server') raise RegisterError(_('Cannot connect to the server.')) @@ -114,10 +141,12 @@ def register_laptop(url=REGISTER_URL): return True -def have_ofw_tree(): + +def _have_ofw_tree(): return os.path.exists('/ofw') -def read_ofw(path): + +def _read_ofw(path): path = os.path.join('/ofw', path) if not os.path.exists(path): return None diff --git a/src/jarabe/desktop/snowflakelayout.py b/src/jarabe/desktop/snowflakelayout.py index 5782cff..e4963ba 100644 --- a/src/jarabe/desktop/snowflakelayout.py +++ b/src/jarabe/desktop/snowflakelayout.py @@ -21,11 +21,14 @@ import hippo from sugar.graphics import style + _BASE_DISTANCE = style.zoom(25) _CHILDREN_FACTOR = style.zoom(3) + class SnowflakeLayout(gobject.GObject, hippo.CanvasLayout): __gtype_name__ = 'SugarSnowflakeLayout' + def __init__(self): gobject.GObject.__init__(self) self._nflakes = 0 diff --git a/src/jarabe/desktop/spreadlayout.py b/src/jarabe/desktop/spreadlayout.py index ffc5bc7..9200361 100644 --- a/src/jarabe/desktop/spreadlayout.py +++ b/src/jarabe/desktop/spreadlayout.py @@ -22,10 +22,13 @@ from sugar.graphics import style from jarabe.desktop.grid import Grid + _CELL_SIZE = 4.0 + class SpreadLayout(gobject.GObject, hippo.CanvasLayout): __gtype_name__ = 'SugarSpreadLayout' + def __init__(self): gobject.GObject.__init__(self) self._box = None @@ -80,4 +83,3 @@ class SpreadLayout(gobject.GObject, hippo.CanvasLayout): def _grid_child_changed_cb(self, grid, child): child.emit_request_changed() - diff --git a/src/jarabe/desktop/transitionbox.py b/src/jarabe/desktop/transitionbox.py index cf9e0d6..4042044 100644 --- a/src/jarabe/desktop/transitionbox.py +++ b/src/jarabe/desktop/transitionbox.py @@ -23,6 +23,7 @@ from sugar.graphics import animator from jarabe.model.buddy import get_owner_instance from jarabe.view.buddyicon import BuddyIcon + class _Animation(animator.Animation): def __init__(self, icon, start_size, end_size): animator.Animation.__init__(self, 0.0, 1.0) @@ -35,8 +36,10 @@ class _Animation(animator.Animation): d = (self.end_size - self.start_size) * current self._icon.props.size = self.start_size + d + class _Layout(gobject.GObject, hippo.CanvasLayout): __gtype_name__ = 'SugarTransitionBoxLayout' + def __init__(self): gobject.GObject.__init__(self) self._box = None @@ -60,12 +63,12 @@ class _Layout(gobject.GObject, hippo.CanvasLayout): y + (height - child_height) / 2, child_width, child_height, origin_changed) + class TransitionBox(hippo.Canvas): __gtype_name__ = 'SugarTransitionBox' __gsignals__ = { - 'completed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])) + 'completed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } def __init__(self): diff --git a/src/jarabe/frame/__init__.py b/src/jarabe/frame/__init__.py index d7aec3d..b3e4b80 100644 --- a/src/jarabe/frame/__init__.py +++ b/src/jarabe/frame/__init__.py @@ -16,8 +16,10 @@ from jarabe.frame.frame import Frame + _view = None + def get_view(): global _view if not _view: diff --git a/src/jarabe/frame/activitiestray.py b/src/jarabe/frame/activitiestray.py index 6bd2a1b..6e08fc0 100644 --- a/src/jarabe/frame/activitiestray.py +++ b/src/jarabe/frame/activitiestray.py @@ -98,7 +98,6 @@ class ActivityButton(RadioToolButton): self._icon.props.pulsing = False - class InviteButton(ToolButton): """Invite to shared activity""" def __init__(self, invite): @@ -209,7 +208,8 @@ class ActivitiesTray(HTray): self._home_model = shell.get_model() self._home_model.connect('activity-added', self.__activity_added_cb) - self._home_model.connect('activity-removed', self.__activity_removed_cb) + self._home_model.connect('activity-removed', + self.__activity_removed_cb) self._home_model.connect('active-activity-changed', self.__activity_changed_cb) self._home_model.connect('tabbing-activity-changed', @@ -313,6 +313,7 @@ class ActivitiesTray(HTray): self.add_item(button) button.show() + class BaseTransferButton(ToolButton): """Button with a notification attached """ @@ -349,6 +350,7 @@ class BaseTransferButton(ToolButton): filetransfer.FT_REASON_LOCAL_STOPPED: self.remove() + class IncomingTransferButton(BaseTransferButton): """UI element representing an ongoing incoming file transfer """ @@ -429,6 +431,7 @@ class IncomingTransferButton(BaseTransferButton): def __dismiss_clicked_cb(self, palette): self.remove() + class OutgoingTransferButton(BaseTransferButton): """UI element representing an ongoing outgoing file transfer """ @@ -446,7 +449,7 @@ class OutgoingTransferButton(BaseTransferButton): break client = gconf.client_get_default() - icon_color = XoColor(client.get_string("/desktop/sugar/user/color")) + icon_color = XoColor(client.get_string('/desktop/sugar/user/color')) self.props.icon_widget.props.xo_color = icon_color self.notif_icon.props.xo_color = icon_color @@ -464,14 +467,14 @@ class OutgoingTransferButton(BaseTransferButton): def __dismiss_clicked_cb(self, palette): self.remove() + class BaseTransferPalette(Palette): """Base palette class for frame or notification icon for file transfers """ - __gtype_name__ = "SugarBaseTransferPalette" + __gtype_name__ = 'SugarBaseTransferPalette' __gsignals__ = { - 'dismiss-clicked': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), + 'dismiss-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } def __init__(self, file_transfer): @@ -526,10 +529,12 @@ class BaseTransferPalette(Palette): total = self._format_size(self.file_transfer.file_size) self.progress_label.props.label = _('%s of %s') % (transferred, total) + class IncomingTransferPalette(BaseTransferPalette): """Palette for frame or notification icon for incoming file transfers """ - __gtype_name__ = "SugarIncomingTransferPalette" + __gtype_name__ = 'SugarIncomingTransferPalette' + def __init__(self, file_transfer): BaseTransferPalette.__init__(self, file_transfer) @@ -652,10 +657,11 @@ class IncomingTransferPalette(BaseTransferPalette): def __dismiss_activate_cb(self, menu_item): self.emit('dismiss-clicked') + class OutgoingTransferPalette(BaseTransferPalette): """Palette for frame or notification icon for outgoing file transfers """ - __gtype_name__ = "SugarOutgoingTransferPalette" + __gtype_name__ = 'SugarOutgoingTransferPalette' def __init__(self, file_transfer): BaseTransferPalette.__init__(self, file_transfer) diff --git a/src/jarabe/frame/clipboard.py b/src/jarabe/frame/clipboard.py index 3b9f745..be2b902 100644 --- a/src/jarabe/frame/clipboard.py +++ b/src/jarabe/frame/clipboard.py @@ -26,6 +26,10 @@ from sugar import mime from jarabe.frame.clipboardobject import ClipboardObject, Format + +_instance = None + + class Clipboard(gobject.GObject): __gsignals__ = { @@ -34,7 +38,7 @@ class Clipboard(gobject.GObject): 'object-deleted': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([int])), 'object-state-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([object])) + ([object])), } def __init__(self): @@ -69,7 +73,7 @@ class Clipboard(gobject.GObject): + ' with path at ' + new_uri) else: cb_object.add_format(Format(format_type, data, on_disk)) - logging.debug('Added in-memory format of type ' + format_type + '.') + logging.debug('Added in-memory format of type %s.', format_type) self.emit('object-state-changed', cb_object) @@ -82,9 +86,9 @@ class Clipboard(gobject.GObject): def set_object_percent(self, object_id, percent): cb_object = self._objects[object_id] if percent < 0 or percent > 100: - raise ValueError("invalid percentage") + raise ValueError('invalid percentage') if cb_object.get_percent() > percent: - raise ValueError("invalid percentage; less than current percent") + raise ValueError('invalid percentage; less than current percent') if cb_object.get_percent() == percent: # ignore setting same percentage return @@ -126,21 +130,21 @@ class Clipboard(gobject.GObject): def _copy_file(self, original_uri): uri = urlparse.urlparse(original_uri) - path_, file_name = os.path.split(uri.path) + path = uri.path # pylint: disable=E1101 + directory_, file_name = os.path.split(path) root, ext = os.path.splitext(file_name) if not ext or ext == '.': - mime_type = mime.get_for_file(uri.path) + mime_type = mime.get_for_file(path) ext = '.' + mime.get_primary_extension(mime_type) f_, new_file_path = tempfile.mkstemp(ext, root) del f_ - shutil.copyfile(uri.path, new_file_path) + shutil.copyfile(path, new_file_path) os.chmod(new_file_path, 0644) return 'file://' + new_file_path -_instance = None def get_instance(): global _instance diff --git a/src/jarabe/frame/clipboardicon.py b/src/jarabe/frame/clipboardicon.py index 279db08..aa72d8a 100644 --- a/src/jarabe/frame/clipboardicon.py +++ b/src/jarabe/frame/clipboardicon.py @@ -31,6 +31,7 @@ from jarabe.frame.frameinvoker import FrameWidgetInvoker from jarabe.frame.notification import NotificationIcon import jarabe.frame + class ClipboardIcon(RadioToolButton): __gtype_name__ = 'SugarClipboardIcon' @@ -71,7 +72,8 @@ class ClipboardIcon(RadioToolButton): def _drag_data_get_cb(self, widget, context, selection, target_type, event_time): - logging.debug('_drag_data_get_cb: requested target ' + selection.target) + logging.debug('_drag_data_get_cb: requested target %s', + selection.target) data = self._cb_object.get_formats()[selection.target].get_data() selection.set(selection.target, 8, data) @@ -79,8 +81,8 @@ class ClipboardIcon(RadioToolButton): logging.debug('ClipboardIcon._put_in_clipboard') if self._cb_object.get_percent() < 100: - raise ValueError('Object is not complete,' \ - ' cannot be put into the clipboard.') + raise ValueError('Object is not complete, cannot be put into the' + ' clipboard.') targets = self._get_targets() if targets: diff --git a/src/jarabe/frame/clipboardmenu.py b/src/jarabe/frame/clipboardmenu.py index b998110..d11538d 100644 --- a/src/jarabe/frame/clipboardmenu.py +++ b/src/jarabe/frame/clipboardmenu.py @@ -35,6 +35,7 @@ from jarabe.frame import clipboard from jarabe.journal import misc from jarabe.model import bundleregistry + class ClipboardMenu(Palette): def __init__(self, cb_object): @@ -212,7 +213,8 @@ class ClipboardMenu(Palette): if most_significant_mime_type == 'text/uri-list': uris = mime.split_uri_list(format_.get_data()) if len(uris) == 1 and uris[0].startswith('file://'): - file_path = urlparse.urlparse(uris[0]).path + parsed_url = urlparse.urlparse(uris[0]) + file_path = parsed_url.path # pylint: disable=E1101 transfer_ownership = False mime_type = mime.get_for_file(file_path) else: @@ -221,7 +223,8 @@ class ClipboardMenu(Palette): mime_type = 'text/uri-list' else: if format_.is_on_disk(): - file_path = urlparse.urlparse(format_.get_data()).path + parsed_url = urlparse.urlparse(format_.get_data()) + file_path = parsed_url.path # pylint: disable=E1101 transfer_ownership = False mime_type = mime.get_for_file(file_path) else: diff --git a/src/jarabe/frame/clipboardobject.py b/src/jarabe/frame/clipboardobject.py index e9403f9..407af2f 100644 --- a/src/jarabe/frame/clipboardobject.py +++ b/src/jarabe/frame/clipboardobject.py @@ -24,6 +24,7 @@ from gettext import gettext as _ from sugar import mime from sugar.bundle.activitybundle import ActivityBundle + class ClipboardObject(object): def __init__(self, object_path, name): @@ -105,15 +106,18 @@ class ClipboardObject(object): if format_ == 'text/uri-list': data = self._formats['text/uri-list'].get_data() uri = urlparse.urlparse(mime.split_uri_list(data)[0], 'file') - if uri.scheme == 'file': - if os.path.exists(uri.path): - format_ = mime.get_for_file(uri.path) + scheme = uri.scheme # pylint: disable=E1101 + if scheme == 'file': + path = uri.path # pylint: disable=E1101 + if os.path.exists(path): + format_ = mime.get_for_file(path) else: - format_ = mime.get_from_file_name(uri.path) + format_ = mime.get_from_file_name(path) logging.debug('Chose %r!', format_) return format_ + class Format(object): def __init__(self, mime_type, data, on_disk): @@ -126,8 +130,9 @@ class Format(object): def destroy(self): if self._on_disk: uri = urlparse.urlparse(self._data) - if os.path.isfile(uri.path): - os.remove(uri.path) + path = uri.path # pylint: disable=E1101 + if os.path.isfile(path): + os.remove(path) def get_type(self): return self._type diff --git a/src/jarabe/frame/clipboardpanelwindow.py b/src/jarabe/frame/clipboardpanelwindow.py index ac324f4..f5d537c 100644 --- a/src/jarabe/frame/clipboardpanelwindow.py +++ b/src/jarabe/frame/clipboardpanelwindow.py @@ -25,6 +25,7 @@ from jarabe.frame.clipboardtray import ClipboardTray from jarabe.frame import clipboard + class ClipboardPanelWindow(FrameWindow): def __init__(self, frame, orientation): FrameWindow.__init__(self, orientation) @@ -35,7 +36,7 @@ class ClipboardPanelWindow(FrameWindow): # NOTE: we need to keep a reference to gtk.Clipboard in order to keep # listening to it. self._clipboard = gtk.Clipboard() - self._clipboard.connect("owner-change", self._owner_change_cb) + self._clipboard.connect('owner-change', self._owner_change_cb) self._clipboard_tray = ClipboardTray() canvas_widget = hippo.CanvasWidget(widget=self._clipboard_tray) @@ -43,14 +44,14 @@ class ClipboardPanelWindow(FrameWindow): # Receiving dnd drops self.drag_dest_set(0, [], 0) - self.connect("drag_motion", self._clipboard_tray.drag_motion_cb) - self.connect("drag_leave", self._clipboard_tray.drag_leave_cb) - self.connect("drag_drop", self._clipboard_tray.drag_drop_cb) - self.connect("drag_data_received", + self.connect('drag_motion', self._clipboard_tray.drag_motion_cb) + self.connect('drag_leave', self._clipboard_tray.drag_leave_cb) + self.connect('drag_drop', self._clipboard_tray.drag_drop_cb) + self.connect('drag_data_received', self._clipboard_tray.drag_data_received_cb) def _owner_change_cb(self, x_clipboard, event): - logging.debug("owner_change_cb") + logging.debug('owner_change_cb') if self._clipboard_tray.owns_clipboard(): return @@ -100,4 +101,3 @@ class ClipboardPanelWindow(FrameWindow): selection.type, selection.data, on_disk=False) - diff --git a/src/jarabe/frame/clipboardtray.py b/src/jarabe/frame/clipboardtray.py index 8beb6a8..f49b799 100644 --- a/src/jarabe/frame/clipboardtray.py +++ b/src/jarabe/frame/clipboardtray.py @@ -27,6 +27,7 @@ from sugar.graphics import style from jarabe.frame import clipboard from jarabe.frame.clipboardicon import ClipboardIcon + class _ContextMap(object): """Maps a drag context to the clipboard object involved in the dragging.""" def __init__(self): @@ -40,8 +41,8 @@ class _ContextMap(object): def get_object_id(self, context): """Retrieves the object_id associated with context. - Will release the association when this function was called as many times - as the number of data_types that this clipboard object contains. + Will release the association when this function was called as many + times as the number of data_types that this clipboard object contains. """ [object_id, data_types_left] = self._context_map[context] @@ -56,6 +57,7 @@ class _ContextMap(object): def has_context(self, context): return context in self._context_map + class ClipboardTray(tray.VTray): MAX_ITEMS = gtk.gdk.screen_height() / style.GRID_CELL_SIZE - 2 @@ -154,7 +156,7 @@ class ClipboardTray(tray.VTray): if 'XdndDirectSave0' in context.targets: window = context.source_window prop_type, format_, filename = \ - window.property_get('XdndDirectSave0','text/plain') + window.property_get('XdndDirectSave0', 'text/plain') # FIXME query the clipboard service for a filename? base_dir = tempfile.gettempdir() @@ -192,12 +194,13 @@ class ClipboardTray(tray.VTray): if selection.data == 'S': window = context.source_window - prop_type, format_, dest = \ - window.property_get('XdndDirectSave0', 'text/plain') + prop_type, format_, dest = window.property_get( + 'XdndDirectSave0', 'text/plain') clipboardservice = clipboard.get_instance() - clipboardservice.add_object_format( \ - object_id, 'XdndDirectSave0', dest, on_disk=True) + clipboardservice.add_object_format(object_id, + 'XdndDirectSave0', + dest, on_disk=True) else: self._add_selection(object_id, selection) @@ -213,4 +216,3 @@ class ClipboardTray(tray.VTray): return True else: return False - diff --git a/src/jarabe/frame/devicestray.py b/src/jarabe/frame/devicestray.py index 72affe3..c5db639 100644 --- a/src/jarabe/frame/devicestray.py +++ b/src/jarabe/frame/devicestray.py @@ -15,14 +15,13 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import os -import sys -import traceback import logging from sugar.graphics import tray from jarabe import config + class DevicesTray(tray.HTray): def __init__(self): tray.HTray.__init__(self, align=tray.ALIGN_TO_END) @@ -35,14 +34,14 @@ class DevicesTray(tray.HTray): locals(), [module_name]) mod.setup(self) except Exception: - logging.error('Exception while loading extension:\n' + \ - ''.join(traceback.format_exception(*sys.exc_info()))) + logging.exception('Exception while loading extension:') def add_device(self, view): index = 0 - relative_index = getattr(view, "FRAME_POSITION_RELATIVE", -1) + relative_index = getattr(view, 'FRAME_POSITION_RELATIVE', -1) for item in self.get_children(): - current_relative_index = getattr(item, "FRAME_POSITION_RELATIVE", 0) + current_relative_index = getattr(item, 'FRAME_POSITION_RELATIVE', + 0) if current_relative_index >= relative_index: index += 1 else: diff --git a/src/jarabe/frame/eventarea.py b/src/jarabe/frame/eventarea.py index 166aaf5..1b5bf86 100644 --- a/src/jarabe/frame/eventarea.py +++ b/src/jarabe/frame/eventarea.py @@ -19,14 +19,14 @@ import gobject import wnck import gconf + _MAX_DELAY = 1000 + class EventArea(gobject.GObject): __gsignals__ = { - 'enter': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), - 'leave': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])) + 'enter': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'leave': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } def __init__(self): @@ -37,10 +37,11 @@ class EventArea(gobject.GObject): self._sids = {} client = gconf.client_get_default() self._edge_delay = client.get_int('/desktop/sugar/frame/edge_delay') - self._corner_delay = client.get_int('/desktop/sugar/frame/corner_delay') + self._corner_delay = client.get_int('/desktop/sugar/frame' + '/corner_delay') right = gtk.gdk.screen_width() - 1 - bottom = gtk.gdk.screen_height() -1 + bottom = gtk.gdk.screen_height() - 1 width = gtk.gdk.screen_width() - 2 height = gtk.gdk.screen_height() - 2 @@ -94,6 +95,7 @@ class EventArea(gobject.GObject): invisible.connect('drag_leave', self._drag_leave_cb) invisible.realize() + # pylint: disable=E1101 invisible.window.set_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.LEAVE_NOTIFY_MASK) diff --git a/src/jarabe/frame/frame.py b/src/jarabe/frame/frame.py index 7dde55b..079eeeb 100644 --- a/src/jarabe/frame/frame.py +++ b/src/jarabe/frame/frame.py @@ -35,6 +35,7 @@ from jarabe.frame.clipboardpanelwindow import ClipboardPanelWindow from jarabe.frame.notification import NotificationIcon, NotificationWindow from jarabe.model import notifications + TOP_RIGHT = 0 TOP_LEFT = 1 BOTTOM_RIGHT = 2 @@ -43,6 +44,7 @@ BOTTOM_LEFT = 3 _FRAME_HIDING_DELAY = 500 _NOTIFICATION_DURATION = 5000 + class _Animation(animator.Animation): def __init__(self, frame, end): start = frame.current_position @@ -52,6 +54,7 @@ class _Animation(animator.Animation): def next_frame(self, current): self._frame.move(current) + class _MouseListener(object): def __init__(self, frame): self._frame = frame @@ -79,6 +82,7 @@ class _MouseListener(object): self._hide_sid = gobject.timeout_add( _FRAME_HIDING_DELAY, self._hide_frame_timeout_cb) + class _KeyListener(object): def __init__(self, frame): self._frame = frame @@ -90,13 +94,14 @@ class _KeyListener(object): else: self._frame.show(Frame.MODE_KEYBOARD) + class Frame(object): - MODE_MOUSE = 0 + MODE_MOUSE = 0 MODE_KEYBOARD = 1 MODE_NON_INTERACTIVE = 2 def __init__(self): - logging.debug("STARTUP: Loading the frame") + logging.debug('STARTUP: Loading the frame') self.mode = None self._palette_group = palettegroup.get_group('frame') @@ -173,12 +178,12 @@ class Frame(object): def _create_top_panel(self): panel = self._create_panel(gtk.POS_TOP) - # TODO: setting box_width and hippo.PACK_EXPAND looks like a hack to me. - # Why hippo isn't respecting the request size of these controls? + # TODO: setting box_width and hippo.PACK_EXPAND looks like a hack to + # me. Why hippo isn't respecting the request size of these controls? zoom_toolbar = ZoomToolbar() panel.append(hippo.CanvasWidget(widget=zoom_toolbar, - box_width=4*style.GRID_CELL_SIZE)) + box_width=4 * style.GRID_CELL_SIZE)) zoom_toolbar.show() activities_tray = ActivitiesTray() @@ -193,7 +198,8 @@ class Frame(object): # TODO: same issue as in _create_top_panel() devices_tray = DevicesTray() - panel.append(hippo.CanvasWidget(widget=devices_tray), hippo.PACK_EXPAND) + panel.append(hippo.CanvasWidget(widget=devices_tray), + hippo.PACK_EXPAND) devices_tray.show() return panel @@ -348,4 +354,3 @@ class Frame(object): # Do nothing for now. Our notification UI is so simple, there's no # point yet. pass - diff --git a/src/jarabe/frame/frameinvoker.py b/src/jarabe/frame/frameinvoker.py index e4a13e1..a4abfa8 100644 --- a/src/jarabe/frame/frameinvoker.py +++ b/src/jarabe/frame/frameinvoker.py @@ -19,6 +19,7 @@ import gtk from sugar.graphics import style from sugar.graphics.palette import WidgetInvoker + def _get_screen_area(): frame_thickness = style.GRID_CELL_SIZE @@ -28,6 +29,7 @@ def _get_screen_area(): return gtk.gdk.Rectangle(x, y, width, height) + class FrameWidgetInvoker(WidgetInvoker): def __init__(self, widget): WidgetInvoker.__init__(self, widget, widget.child) diff --git a/src/jarabe/frame/framewindow.py b/src/jarabe/frame/framewindow.py index a7d8fe7..c77e76c 100644 --- a/src/jarabe/frame/framewindow.py +++ b/src/jarabe/frame/framewindow.py @@ -19,6 +19,7 @@ import hippo from sugar.graphics import style + class FrameWindow(gtk.Window): __gtype_name__ = 'SugarFrameWindow' diff --git a/src/jarabe/frame/friendstray.py b/src/jarabe/frame/friendstray.py index 141505b..31a9809 100644 --- a/src/jarabe/frame/friendstray.py +++ b/src/jarabe/frame/friendstray.py @@ -24,6 +24,7 @@ from jarabe.model import shell from jarabe.model.buddy import get_owner_instance from jarabe.model import neighborhood + class FriendIcon(TrayIcon): def __init__(self, buddy): TrayIcon.__init__(self, icon_name='computer-xo', @@ -34,6 +35,7 @@ class FriendIcon(TrayIcon): self.palette.props.icon_visible = False self.palette.set_group_id('frame') + class FriendsTray(VTray): def __init__(self): VTray.__init__(self) @@ -48,7 +50,7 @@ class FriendsTray(VTray): self.__neighborhood_activity_added_cb) def add_buddy(self, buddy): - if self._buddies.has_key(buddy.props.key): + if buddy.props.key in self._buddies: return icon = FriendIcon(buddy) @@ -58,7 +60,7 @@ class FriendsTray(VTray): self._buddies[buddy.props.key] = icon def remove_buddy(self, buddy): - if not self._buddies.has_key(buddy.props.key): + if buddy.props.key not in self._buddies: return self.remove_item(self._buddies[buddy.props.key]) diff --git a/src/jarabe/frame/notification.py b/src/jarabe/frame/notification.py index 83dc27e..3471e2c 100644 --- a/src/jarabe/frame/notification.py +++ b/src/jarabe/frame/notification.py @@ -22,13 +22,14 @@ from sugar.graphics.xocolor import XoColor from jarabe.view.pulsingicon import PulsingIcon + class NotificationIcon(gtk.EventBox): __gtype_name__ = 'SugarNotificationIcon' __gproperties__ = { - 'xo-color' : (object, None, None, gobject.PARAM_READWRITE), - 'icon-name' : (str, None, None, None, gobject.PARAM_READWRITE), - 'icon-filename' : (str, None, None, None, gobject.PARAM_READWRITE) + 'xo-color': (object, None, None, gobject.PARAM_READWRITE), + 'icon-name': (str, None, None, None, gobject.PARAM_READWRITE), + 'icon-filename': (str, None, None, None, gobject.PARAM_READWRITE), } _PULSE_TIMEOUT = 3 @@ -45,7 +46,8 @@ class NotificationIcon(gtk.EventBox): self.add(self._icon) self._icon.show() - gobject.timeout_add_seconds(self._PULSE_TIMEOUT, self.__stop_pulsing_cb) + gobject.timeout_add_seconds(self._PULSE_TIMEOUT, + self.__stop_pulsing_cb) self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE) @@ -80,6 +82,7 @@ class NotificationIcon(gtk.EventBox): palette = property(_get_palette, _set_palette) + class NotificationWindow(gtk.Window): __gtype_name__ = 'SugarNotificationWindow' @@ -97,4 +100,3 @@ class NotificationWindow(gtk.Window): color = gtk.gdk.color_parse(style.COLOR_TOOLBAR_GREY.get_html()) self.modify_bg(gtk.STATE_NORMAL, color) - diff --git a/src/jarabe/frame/zoomtoolbar.py b/src/jarabe/frame/zoomtoolbar.py index 2ed3c54..6c10c61 100644 --- a/src/jarabe/frame/zoomtoolbar.py +++ b/src/jarabe/frame/zoomtoolbar.py @@ -26,6 +26,7 @@ from sugar.graphics.radiotoolbutton import RadioToolButton from jarabe.frame.frameinvoker import FrameWidgetInvoker from jarabe.model import shell + class ZoomToolbar(gtk.Toolbar): def __init__(self): gtk.Toolbar.__init__(self) @@ -86,4 +87,3 @@ class ZoomToolbar(gtk.Toolbar): self._activity_button.props.active = True else: raise ValueError('Invalid zoom level: %r' % (new_level)) - diff --git a/src/jarabe/intro/Makefile.am b/src/jarabe/intro/Makefile.am index a9fb96b..2ea7cea 100644 --- a/src/jarabe/intro/Makefile.am +++ b/src/jarabe/intro/Makefile.am @@ -1,7 +1,3 @@ -imagedir = $(pythondir)/jarabe/intro -image_DATA = default-picture.png - -EXTRA_DIST = $(conf_DATA) $(image_DATA) sugardir = $(pythondir)/jarabe/intro sugar_PYTHON = \ __init__.py \ diff --git a/src/jarabe/intro/__init__.py b/src/jarabe/intro/__init__.py index ca4f64d..d2932f1 100644 --- a/src/jarabe/intro/__init__.py +++ b/src/jarabe/intro/__init__.py @@ -8,6 +8,7 @@ from sugar.profile import get_profile from jarabe.intro.window import IntroWindow from jarabe.intro.window import create_profile + def check_profile(): profile = get_profile() diff --git a/src/jarabe/intro/colorpicker.py b/src/jarabe/intro/colorpicker.py index a939857..997199b 100644 --- a/src/jarabe/intro/colorpicker.py +++ b/src/jarabe/intro/colorpicker.py @@ -20,6 +20,7 @@ from sugar.graphics.icon import CanvasIcon from sugar.graphics import style from sugar.graphics.xocolor import XoColor + class ColorPicker(hippo.CanvasBox, hippo.CanvasItem): def __init__(self, **kwargs): hippo.CanvasBox.__init__(self, **kwargs) diff --git a/src/jarabe/intro/default-picture.png b/src/jarabe/intro/default-picture.png Binary files differdeleted file mode 100644 index e26b9b0..0000000 --- a/src/jarabe/intro/default-picture.png +++ /dev/null diff --git a/src/jarabe/intro/window.py b/src/jarabe/intro/window.py index 35c0cda..df19fbf 100644 --- a/src/jarabe/intro/window.py +++ b/src/jarabe/intro/window.py @@ -32,38 +32,34 @@ from sugar.graphics.xocolor import XoColor from jarabe.intro import colorpicker + _BACKGROUND_COLOR = style.COLOR_WHITE -def create_profile(name, color=None, pixbuf=None): - if not pixbuf: - path = os.path.join(os.path.dirname(__file__), 'default-picture.png') - pixbuf = gtk.gdk.pixbuf_new_from_file(path) +def create_profile(name, color=None): if not color: color = XoColor() - icon_path = os.path.join(env.get_profile_path(), "buddy-icon.jpg") - pixbuf.save(icon_path, "jpeg", {"quality":"85"}) - client = gconf.client_get_default() - client.set_string("/desktop/sugar/user/nick", name) - client.set_string("/desktop/sugar/user/color", color.to_string()) + client.set_string('/desktop/sugar/user/nick', name) + client.set_string('/desktop/sugar/user/color', color.to_string()) + client.suggest_sync() # Generate keypair import commands - keypath = os.path.join(env.get_profile_path(), "owner.key") + keypath = os.path.join(env.get_profile_path(), 'owner.key') if not os.path.isfile(keypath): cmd = "ssh-keygen -q -t dsa -f %s -C '' -N ''" % keypath (s, o) = commands.getstatusoutput(cmd) if s != 0: - logging.error("Could not generate key pair: %d %s", s, o) + logging.error('Could not generate key pair: %d %s', s, o) else: - logging.error("Keypair exists, skip generation.") + logging.error('Keypair exists, skip generation.') + class _Page(hippo.CanvasBox): __gproperties__ = { - 'valid' : (bool, None, None, False, - gobject.PARAM_READABLE) + 'valid': (bool, None, None, False, gobject.PARAM_READABLE), } def __init__(self, **kwargs): @@ -81,6 +77,7 @@ class _Page(hippo.CanvasBox): def activate(self): pass + class _NamePage(_Page): def __init__(self, intro): _Page.__init__(self, xalign=hippo.ALIGNMENT_CENTER, @@ -90,7 +87,7 @@ class _NamePage(_Page): self._intro = intro - label = hippo.CanvasText(text=_("Name:")) + label = hippo.CanvasText(text=_('Name:')) self.append(label) self._entry = CanvasEntry(box_width=style.zoom(300)) @@ -118,6 +115,7 @@ class _NamePage(_Page): def activate(self): self._entry.props.widget.grab_focus() + class _ColorPage(_Page): def __init__(self, **kwargs): _Page.__init__(self, xalign=hippo.ALIGNMENT_CENTER, @@ -125,7 +123,7 @@ class _ColorPage(_Page): spacing=style.DEFAULT_SPACING, yalign=hippo.ALIGNMENT_CENTER, **kwargs) - self._label = hippo.CanvasText(text=_("Click to change color:"), + self._label = hippo.CanvasText(text=_('Click to change color:'), xalign=hippo.ALIGNMENT_CENTER) self.append(self._label) @@ -138,10 +136,11 @@ class _ColorPage(_Page): def get_color(self): return self._cp.get_color() + class _IntroBox(hippo.CanvasBox): __gsignals__ = { 'done': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])) + ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), } PAGE_NAME = 0 @@ -166,13 +165,9 @@ class _IntroBox(hippo.CanvasBox): self._page = self.PAGE_COLOR if default_nick == 'system': pwd_entry = pwd.getpwuid(os.getuid()) - if pwd_entry.pw_gecos: - nick = pwd_entry.pw_gecos.split(',')[0] - self._name_page.set_name(nick) - else: - self._name_page.set_name(pwd_entry.pw_name) - else: - self._name_page.set_name(default_nick) + default_nick = (pwd_entry.pw_gecos.split(',')[0] or + pwd_entry.pw_name) + self._name_page.set_name(default_nick) self._setup_page() @@ -255,6 +250,7 @@ class _IntroBox(hippo.CanvasBox): self.emit('done', name, color) + class IntroWindow(gtk.Window): def __init__(self): gtk.Window.__init__(self) @@ -282,16 +278,16 @@ class IntroWindow(gtk.Window): return False def __key_press_cb(self, widget, event): - if gtk.gdk.keyval_name(event.keyval) == "Return": + if gtk.gdk.keyval_name(event.keyval) == 'Return': self._intro_box.next() return True - elif gtk.gdk.keyval_name(event.keyval) == "Escape": + elif gtk.gdk.keyval_name(event.keyval) == 'Escape': self._intro_box.back() return True return False -if __name__ == "__main__": +if __name__ == '__main__': w = IntroWindow() w.show() w.connect('destroy', gtk.main_quit) diff --git a/src/jarabe/journal/Makefile.am b/src/jarabe/journal/Makefile.am index f4bf273..ba29062 100644 --- a/src/jarabe/journal/Makefile.am +++ b/src/jarabe/journal/Makefile.am @@ -6,6 +6,7 @@ sugar_PYTHON = \ journalactivity.py \ journalentrybundle.py \ journaltoolbox.py \ + journalwindow.py \ keepicon.py \ listmodel.py \ listview.py \ diff --git a/src/jarabe/journal/detailview.py b/src/jarabe/journal/detailview.py index b4a2339..aa8c039 100644 --- a/src/jarabe/journal/detailview.py +++ b/src/jarabe/journal/detailview.py @@ -27,11 +27,12 @@ from sugar.graphics.icon import CanvasIcon from jarabe.journal.expandedentry import ExpandedEntry from jarabe.journal import model + class DetailView(gtk.VBox): __gtype_name__ = 'DetailView' __gsignals__ = { - 'go-back-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])) + 'go-back-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } def __init__(self, **kwargs): @@ -84,6 +85,7 @@ class DetailView(gtk.VBox): metadata = gobject.property( type=object, getter=get_metadata, setter=set_metadata) + class BackBar(hippo.CanvasBox): def __init__(self): hippo.CanvasBox.__init__(self, diff --git a/src/jarabe/journal/expandedentry.py b/src/jarabe/journal/expandedentry.py index 725c0f9..fe2f320 100644 --- a/src/jarabe/journal/expandedentry.py +++ b/src/jarabe/journal/expandedentry.py @@ -36,6 +36,7 @@ from jarabe.journal.palettes import ObjectPalette, BuddyPalette from jarabe.journal import misc from jarabe.journal import model + class Separator(hippo.CanvasBox, hippo.CanvasItem): def __init__(self, orientation): hippo.CanvasBox.__init__(self, @@ -46,6 +47,7 @@ class Separator(hippo.CanvasBox, hippo.CanvasItem): else: self.props.box_height = style.LINE_WIDTH + class BuddyList(hippo.CanvasBox): def __init__(self, buddies): hippo.CanvasBox.__init__(self, xalign=hippo.ALIGNMENT_START, @@ -61,6 +63,7 @@ class BuddyList(hippo.CanvasBox): hbox.append(icon) self.append(hbox) + class ExpandedEntry(hippo.CanvasBox): def __init__(self): hippo.CanvasBox.__init__(self) @@ -209,9 +212,7 @@ class ExpandedEntry(hippo.CanvasBox): height = style.zoom(240) box = hippo.CanvasBox() - if self._metadata.has_key('preview') and \ - len(self._metadata['preview']) > 4: - + if len(self._metadata.get('preview', '')) > 4: if self._metadata['preview'][1:4] == 'PNG': preview_data = self._metadata['preview'] else: @@ -280,10 +281,15 @@ class ExpandedEntry(hippo.CanvasBox): def _format_date(self): if 'timestamp' in self._metadata: - timestamp = float(self._metadata['timestamp']) - return time.strftime('%x', time.localtime(timestamp)) - else: - return _('No date') + try: + timestamp = float(self._metadata['timestamp']) + except (ValueError, TypeError): + logging.warning('Invalid timestamp for %r: %r', + self._metadata['uid'], + self._metadata['timestamp']) + else: + return time.strftime('%x', time.localtime(timestamp)) + return _('No date') def _create_buddy_list(self): @@ -301,8 +307,7 @@ class ExpandedEntry(hippo.CanvasBox): vbox.append(text) - if self._metadata.has_key('buddies') and \ - self._metadata['buddies']: + if self._metadata.get('buddies'): buddies = simplejson.loads(self._metadata['buddies']).values() vbox.append(BuddyList(buddies)) return vbox diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py index 44cc018..a33038a 100644 --- a/src/jarabe/journal/journalactivity.py +++ b/src/jarabe/journal/journalactivity.py @@ -17,8 +17,6 @@ import logging from gettext import gettext as _ -import sys -import traceback import uuid import gtk @@ -44,6 +42,8 @@ from jarabe.journal.journalentrybundle import JournalEntryBundle from jarabe.journal.objectchooser import ObjectChooser from jarabe.journal.modalalert import ModalAlert from jarabe.journal import model +from jarabe.journal.journalwindow import JournalWindow + J_DBUS_SERVICE = 'org.laptop.Journal' J_DBUS_INTERFACE = 'org.laptop.Journal' @@ -52,6 +52,9 @@ J_DBUS_PATH = '/org/laptop/Journal' _SPACE_TRESHOLD = 52428800 _BUNDLE_ID = 'org.laptop.JournalActivity' +_journal = None + + class JournalActivityDBusService(dbus.service.Object): def __init__(self, parent): self._parent = parent @@ -81,7 +84,8 @@ class JournalActivityDBusService(dbus.service.Object): chooser.destroy() del chooser - @dbus.service.method(J_DBUS_INTERFACE, in_signature='is', out_signature='s') + @dbus.service.method(J_DBUS_INTERFACE, in_signature='is', + out_signature='s') def ChooseObject(self, parent_xid, what_filter=''): chooser_id = uuid.uuid4().hex if parent_xid > 0: @@ -94,18 +98,19 @@ class JournalActivityDBusService(dbus.service.Object): return chooser_id - @dbus.service.signal(J_DBUS_INTERFACE, signature="ss") + @dbus.service.signal(J_DBUS_INTERFACE, signature='ss') def ObjectChooserResponse(self, chooser_id, object_id): pass - @dbus.service.signal(J_DBUS_INTERFACE, signature="s") + @dbus.service.signal(J_DBUS_INTERFACE, signature='s') def ObjectChooserCancelled(self, chooser_id): pass -class JournalActivity(Window): + +class JournalActivity(JournalWindow): def __init__(self): - logging.debug("STARTUP: Loading the journal") - Window.__init__(self) + logging.debug('STARTUP: Loading the journal') + JournalWindow.__init__(self) self.set_title(_('Journal')) @@ -223,8 +228,7 @@ class JournalActivity(Window): try: self._detail_toolbox.entry_toolbar.set_metadata(metadata) except Exception: - logging.error('Exception while displaying entry:\n' + \ - ''.join(traceback.format_exception(*sys.exc_info()))) + logging.exception('Exception while displaying entry:') self.set_toolbar_box(self._detail_toolbox) self._detail_toolbox.show() @@ -232,8 +236,7 @@ class JournalActivity(Window): try: self._detail_view.props.metadata = metadata except Exception: - logging.error('Exception while displaying entry:\n' + \ - ''.join(traceback.format_exception(*sys.exc_info()))) + logging.exception('Exception while displaying entry:') self.set_canvas(self._secondary_view) self._secondary_view.show() @@ -327,11 +330,11 @@ class JournalActivity(Window): self._list_view.set_is_visible(visible) def _check_available_space(self): - ''' Check available space on device + """Check available space on device If the available space is below 50MB an alert will be shown which encourages to delete old journal entries. - ''' + """ if self._critical_space_alert: return @@ -358,7 +361,6 @@ class JournalActivity(Window): self.show_main_view() self.search_grab_focus() -_journal = None def get_journal(): global _journal @@ -367,6 +369,6 @@ def get_journal(): _journal.show() return _journal + def start(): get_journal() - diff --git a/src/jarabe/journal/journalentrybundle.py b/src/jarabe/journal/journalentrybundle.py index 41777c7..c220c09 100644 --- a/src/jarabe/journal/journalentrybundle.py +++ b/src/jarabe/journal/journalentrybundle.py @@ -25,6 +25,7 @@ from sugar.bundle.bundle import Bundle, MalformedBundleException from jarabe.journal import model + class JournalEntryBundle(Bundle): """A Journal entry bundle @@ -41,7 +42,7 @@ class JournalEntryBundle(Bundle): Bundle.__init__(self, path) def install(self, uid=''): - if os.environ.has_key('SUGAR_ACTIVITY_ROOT'): + if 'SUGAR_ACTIVITY_ROOT' in os.environ: install_dir = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'data') else: @@ -91,4 +92,3 @@ class JournalEntryBundle(Bundle): def is_installed(self): # These bundles can be reinstalled as many times as desired. return False - diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py index cbf105d..cdf998d 100644 --- a/src/jarabe/journal/journaltoolbox.py +++ b/src/jarabe/journal/journaltoolbox.py @@ -43,6 +43,7 @@ from jarabe.model import bundleregistry from jarabe.journal import misc from jarabe.journal import model + _AUTOSEARCH_TIMEOUT = 1000 _ACTION_ANYTIME = 0 @@ -68,13 +69,13 @@ class MainToolbox(Toolbox): self.add_toolbar(_('Search'), self.search_toolbar) self.search_toolbar.show() + class SearchToolbar(gtk.Toolbar): __gtype_name__ = 'SearchToolbar' __gsignals__ = { - 'query-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([object])) + 'query-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), } def __init__(self): @@ -109,8 +110,6 @@ class SearchToolbar(gtk.Toolbar): self.insert(tool_item, -1) tool_item.show() - self._add_separator(expand=True) - self._sorting_button = SortingButton() self._sorting_button.connect('clicked', self.__sorting_button_clicked_cb) @@ -164,17 +163,6 @@ class SearchToolbar(gtk.Toolbar): with_search.connect('changed', self._combo_changed_cb) return with_search - def _add_separator(self, expand=False): - separator = gtk.SeparatorToolItem() - separator.props.draw = False - if expand: - separator.set_expand(True) - else: - separator.set_size_request(style.GRID_CELL_SIZE, - style.GRID_CELL_SIZE) - self.insert(separator, -1) - separator.show() - def _add_widget(self, widget, expand=False): tool_item = gtk.ToolItem() tool_item.set_expand(expand) @@ -355,6 +343,7 @@ class SearchToolbar(gtk.Toolbar): self._when_search_combo.set_active(0) self._favorite_button.props.active = False + class DetailToolbox(Toolbox): def __init__(self): Toolbox.__init__(self) @@ -363,12 +352,13 @@ class DetailToolbox(Toolbox): self.add_toolbar('', self.entry_toolbar) self.entry_toolbar.show() + class EntryToolbar(gtk.Toolbar): __gsignals__ = { - 'volume-error': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([str, str])) + 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str, str])), } + def __init__(self): gtk.Toolbar.__init__(self) @@ -451,7 +441,7 @@ class EntryToolbar(gtk.Toolbar): model.copy(self._metadata, mount_point) except IOError, e: logging.exception('Error while copying the entry. %s', e.strerror) - self.emit('volume-error', + self.emit('volume-error', _('Error while copying the entry. %s') % e.strerror, _('Error')) diff --git a/src/jarabe/journal/journalwindow.py b/src/jarabe/journal/journalwindow.py new file mode 100644 index 0000000..31bc790 --- /dev/null +++ b/src/jarabe/journal/journalwindow.py @@ -0,0 +1,33 @@ +#Copyright (C) 2010 Software for Education, Entertainment and Training +#Activities +# +# 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 sugar.graphics.window import Window + +_journal_window = None + + +class JournalWindow(Window): + + def __init__(self): + + global _journal_window + Window.__init__(self) + _journal_window = self + + +def get_journal_window(): + return _journal_window diff --git a/src/jarabe/journal/keepicon.py b/src/jarabe/journal/keepicon.py index 2c692c6..1253afc 100644 --- a/src/jarabe/journal/keepicon.py +++ b/src/jarabe/journal/keepicon.py @@ -22,6 +22,7 @@ from sugar.graphics.icon import CanvasIcon from sugar.graphics import style from sugar.graphics.xocolor import XoColor + class KeepIcon(CanvasIcon): def __init__(self, keep): CanvasIcon.__init__(self, icon_name='emblem-favorite', diff --git a/src/jarabe/journal/listmodel.py b/src/jarabe/journal/listmodel.py index 3378350..3902eba 100644 --- a/src/jarabe/journal/listmodel.py +++ b/src/jarabe/journal/listmodel.py @@ -19,7 +19,6 @@ import logging import simplejson import gobject import gtk -import time from gettext import gettext as _ from sugar.graphics.xocolor import XoColor @@ -29,20 +28,18 @@ from sugar import util from jarabe.journal import model from jarabe.journal import misc + DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore' DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore' DS_DBUS_PATH = '/org/laptop/sugar/DataStore' + class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource): __gtype_name__ = 'JournalListModel' __gsignals__ = { - 'ready': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([])), - 'progress': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([])), + 'ready': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'progress': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } COLUMN_UID = 0 @@ -58,18 +55,20 @@ class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource): COLUMN_BUDDY_2 = 10 COLUMN_BUDDY_3 = 11 - _COLUMN_TYPES = {COLUMN_UID: str, - COLUMN_FAVORITE: bool, - COLUMN_ICON: str, - COLUMN_ICON_COLOR: object, - COLUMN_TITLE: str, - COLUMN_TIMESTAMP: str, - COLUMN_CREATION_TIME: str, - COLUMN_FILESIZE: str, - COLUMN_PROGRESS: int, - COLUMN_BUDDY_1: object, - COLUMN_BUDDY_3: object, - COLUMN_BUDDY_2: object} + _COLUMN_TYPES = { + COLUMN_UID: str, + COLUMN_FAVORITE: bool, + COLUMN_ICON: str, + COLUMN_ICON_COLOR: object, + COLUMN_TITLE: str, + COLUMN_TIMESTAMP: str, + COLUMN_CREATION_TIME: str, + COLUMN_FILESIZE: str, + COLUMN_PROGRESS: int, + COLUMN_BUDDY_1: object, + COLUMN_BUDDY_3: object, + COLUMN_BUDDY_2: object, + } _PAGE_SIZE = 10 @@ -141,11 +140,17 @@ class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource): xo_color = misc.get_icon_color(metadata) self._cached_row.append(xo_color) - title = gobject.markup_escape_text(metadata.get('title', None)) - self._cached_row.append('<b>%s</b>' % title) + title = gobject.markup_escape_text(metadata.get('title', + _('Untitled'))) + self._cached_row.append('<b>%s</b>' % (title, )) - timestamp = int(metadata.get('timestamp', 0)) - self._cached_row.append(util.timestamp_to_elapsed_string(timestamp)) + try: + timestamp = float(metadata.get('timestamp', 0)) + except (TypeError, ValueError): + timestamp_content = _('Unknown') + else: + timestamp_content = util.timestamp_to_elapsed_string(timestamp) + self._cached_row.append(timestamp_content) try: creation_time = float(metadata.get('creation_time')) @@ -162,19 +167,37 @@ class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource): else: self._cached_row.append(util.format_size(size)) - self._cached_row.append(int(metadata.get('progress', 100))) - - if metadata.get('buddies', ''): - buddies = simplejson.loads(metadata['buddies']).values() - else: + try: + progress = int(float(metadata.get('progress', 100))) + except (TypeError, ValueError): + progress = 100 + self._cached_row.append(progress) + + buddies = [] + if metadata.get('buddies'): + try: + buddies = simplejson.loads(metadata['buddies']).values() + except simplejson.decoder.JSONDecodeError, exception: + logging.warning('Cannot decode buddies for %r: %s', + metadata['uid'], exception) + + if not isinstance(buddies, list): + logging.warning('Content of buddies for %r is not a list: %r', + metadata['uid'], buddies) buddies = [] for n_ in xrange(0, 3): if buddies: - nick, color = buddies.pop(0) - self._cached_row.append((nick, XoColor(color))) - else: - self._cached_row.append(None) + try: + nick, color = buddies.pop(0) + except (AttributeError, ValueError), exception: + logging.warning('Malformed buddies for %r: %s', + metadata['uid'], exception) + else: + self._cached_row.append((nick, XoColor(color))) + continue + + self._cached_row.append(None) return self._cached_row[column] @@ -219,4 +242,3 @@ class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource): return True return False - diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py index 3d6281a..0aee1b7 100644 --- a/src/jarabe/journal/listview.py +++ b/src/jarabe/journal/listview.py @@ -34,11 +34,13 @@ from jarabe.journal.palettes import ObjectPalette, BuddyPalette from jarabe.journal import model from jarabe.journal import misc + UPDATE_INTERVAL = 300 MESSAGE_EMPTY_JOURNAL = 0 MESSAGE_NO_MATCH = 1 + class TreeView(gtk.TreeView): __gtype_name__ = 'JournalTreeView' @@ -47,8 +49,8 @@ class TreeView(gtk.TreeView): self.set_headers_visible(False) def do_size_request(self, requisition): - # HACK: We tell the model that the view is just resizing so it can avoid - # hitting both D-Bus and disk. + # HACK: We tell the model that the view is just resizing so it can + # avoid hitting both D-Bus and disk. tree_model = self.get_model() if tree_model is not None: tree_model.view_is_resizing = True @@ -58,13 +60,12 @@ class TreeView(gtk.TreeView): if tree_model is not None: tree_model.view_is_resizing = False + class BaseListView(gtk.Bin): __gtype_name__ = 'JournalBaseListView' __gsignals__ = { - 'clear-clicked': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([])) + 'clear-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } def __init__(self): @@ -81,7 +82,8 @@ class BaseListView(gtk.Bin): self.connect('destroy', self.__destroy_cb) self._scrolled_window = gtk.ScrolledWindow() - self._scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self._scrolled_window.set_policy(gtk.POLICY_NEVER, + gtk.POLICY_AUTOMATIC) self.add(self._scrolled_window) self._scrolled_window.show() @@ -141,7 +143,8 @@ class BaseListView(gtk.Bin): column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED column.props.fixed_width = self.cell_icon.props.width column.pack_start(self.cell_icon) - column.add_attribute(self.cell_icon, 'file-name', ListModel.COLUMN_ICON) + column.add_attribute(self.cell_icon, 'file-name', + ListModel.COLUMN_ICON) column.add_attribute(self.cell_icon, 'xo-color', ListModel.COLUMN_ICON_COLOR) self.tree_view.append_column(column) @@ -163,7 +166,8 @@ class BaseListView(gtk.Bin): buddies_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED self.tree_view.append_column(buddies_column) - for column_index in [ListModel.COLUMN_BUDDY_1, ListModel.COLUMN_BUDDY_2, + for column_index in [ListModel.COLUMN_BUDDY_1, + ListModel.COLUMN_BUDDY_2, ListModel.COLUMN_BUDDY_3]: cell_icon = CellRendererBuddy(self.tree_view, column_index=column_index) @@ -197,7 +201,8 @@ class BaseListView(gtk.Bin): self.sort_column.props.resizable = True self.sort_column.props.clickable = True self.sort_column.pack_start(cell_text) - self.sort_column.add_attribute(cell_text, 'text', ListModel.COLUMN_TIMESTAMP) + self.sort_column.add_attribute(cell_text, 'text', + ListModel.COLUMN_TIMESTAMP) self.tree_view.append_column(self.sort_column) def _get_width_for_string(self, text): @@ -321,12 +326,9 @@ class BaseListView(gtk.Bin): def _is_query_empty(self): # FIXME: This is a hack, we shouldn't have to update this every time # a new search term is added. - if self._query.get('query', '') or self._query.get('mime_type', '') or \ - self._query.get('keep', '') or self._query.get('mtime', '') or \ - self._query.get('activity', ''): - return False - else: - return True + return not (self._query.get('query') or self._query.get('mime_type') or + self._query.get('keep') or self._query.get('mtime') or + self._query.get('activity')) def __model_progress_cb(self, tree_model): if self._progress_bar is None: @@ -370,8 +372,8 @@ class BaseListView(gtk.Bin): icon = CanvasIcon(size=style.LARGE_ICON_SIZE, icon_name='activity-journal', - stroke_color = style.COLOR_BUTTON_GREY.get_svg(), - fill_color = style.COLOR_TRANSPARENT.get_svg()) + stroke_color=style.COLOR_BUTTON_GREY.get_svg(), + fill_color=style.COLOR_TRANSPARENT.get_svg()) box.append(icon) if message == MESSAGE_EMPTY_JOURNAL: @@ -384,7 +386,7 @@ class BaseListView(gtk.Bin): text = hippo.CanvasText(text=text, xalign=hippo.ALIGNMENT_CENTER, font_desc=style.FONT_BOLD.get_pango_desc(), - color = style.COLOR_BUTTON_GREY.get_int()) + color=style.COLOR_BUTTON_GREY.get_int()) box.append(text) if message == MESSAGE_NO_MATCH: @@ -420,7 +422,7 @@ class BaseListView(gtk.Bin): while True: x, y, width, height = self.tree_view.get_cell_area(path, - self.sort_column) + self.sort_column) x, y = self.tree_view.convert_tree_to_widget_coords(x, y) self.tree_view.queue_draw_area(x, y, width, height) if path == end_path: @@ -460,13 +462,13 @@ class BaseListView(gtk.Bin): self.update_dates() return True + class ListView(BaseListView): __gtype_name__ = 'JournalListView' __gsignals__ = { - 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([object])) + 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), } def __init__(self): @@ -538,6 +540,7 @@ class ListView(BaseListView): def __editing_canceled_cb(self, cell): self.cell_title.props.editable = False + class CellRendererFavorite(CellRendererIcon): __gtype_name__ = 'JournalCellRendererFavorite' @@ -552,6 +555,7 @@ class CellRendererFavorite(CellRendererIcon): self.props.prelit_stroke_color = style.COLOR_BUTTON_GREY.get_svg() self.props.prelit_fill_color = style.COLOR_BUTTON_GREY.get_svg() + class CellRendererDetail(CellRendererIcon): __gtype_name__ = 'JournalCellRendererDetail' @@ -568,12 +572,12 @@ class CellRendererDetail(CellRendererIcon): self.props.prelit_stroke_color = style.COLOR_TRANSPARENT.get_svg() self.props.prelit_fill_color = style.COLOR_BLACK.get_svg() + class CellRendererActivityIcon(CellRendererIcon): __gtype_name__ = 'JournalCellRendererActivityIcon' __gsignals__ = { - 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, + 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])), } @@ -610,6 +614,7 @@ class CellRendererActivityIcon(CellRendererIcon): show_palette = gobject.property(type=bool, default=True, setter=set_show_palette) + class CellRendererBuddy(CellRendererIcon): __gtype_name__ = 'JournalCellRendererBuddy' @@ -643,4 +648,3 @@ class CellRendererBuddy(CellRendererIcon): self.props.xo_color = xo_color buddy = gobject.property(type=object, setter=set_buddy) - diff --git a/src/jarabe/journal/misc.py b/src/jarabe/journal/misc.py index 32a2847..1431d5f 100644 --- a/src/jarabe/journal/misc.py +++ b/src/jarabe/journal/misc.py @@ -27,8 +27,10 @@ from sugar.activity import activityfactory from sugar.activity.activityhandle import ActivityHandle from sugar.graphics.icon import get_icon_file_name from sugar.graphics.xocolor import XoColor +from sugar.graphics.alert import ConfirmationAlert from sugar import mime from sugar.bundle.activitybundle import ActivityBundle +from sugar.bundle.bundle import AlreadyInstalledException from sugar.bundle.contentbundle import ContentBundle from sugar import util @@ -36,6 +38,8 @@ from jarabe.view import launcher from jarabe.model import bundleregistry, shell from jarabe.journal.journalentrybundle import JournalEntryBundle from jarabe.journal import model +from jarabe.journal import journalwindow + def _get_icon_for_mime(mime_type): generic_types = mime.get_all_generic_types() @@ -52,6 +56,7 @@ def _get_icon_for_mime(mime_type): if file_name is not None: return file_name + def get_icon_name(metadata): file_name = None @@ -81,35 +86,46 @@ def get_icon_name(metadata): return file_name + def get_date(metadata): """ Convert from a string in iso format to a more human-like format. """ - if metadata.has_key('timestamp'): - timestamp = float(metadata['timestamp']) - return util.timestamp_to_elapsed_string(timestamp) - elif metadata.has_key('mtime'): - ti = time.strptime(metadata['mtime'], "%Y-%m-%dT%H:%M:%S") - return util.timestamp_to_elapsed_string(time.mktime(ti)) - else: - return _('No date') + if 'timestamp' in metadata: + try: + timestamp = float(metadata['timestamp']) + except (TypeError, ValueError): + logging.warning('Invalid timestamp: %r', metadata['timestamp']) + else: + return util.timestamp_to_elapsed_string(timestamp) + + if 'mtime' in metadata: + try: + ti = time.strptime(metadata['mtime'], '%Y-%m-%dT%H:%M:%S') + except (TypeError, ValueError): + logging.warning('Invalid mtime: %r', metadata['mtime']) + else: + return util.timestamp_to_elapsed_string(time.mktime(ti)) + + return _('No date') + def get_bundle(metadata): try: if is_activity_bundle(metadata): - file_path = util.TempFilePath(model.get_file(metadata['uid'])) + file_path = model.get_file(metadata['uid']) if not os.path.exists(file_path): logging.warning('Invalid path: %r', file_path) return None return ActivityBundle(file_path) elif is_content_bundle(metadata): - file_path = util.TempFilePath(model.get_file(metadata['uid'])) + file_path = model.get_file(metadata['uid']) if not os.path.exists(file_path): logging.warning('Invalid path: %r', file_path) return None return ContentBundle(file_path) elif is_journal_bundle(metadata): - file_path = util.TempFilePath(model.get_file(metadata['uid'])) + file_path = model.get_file(metadata['uid']) if not os.path.exists(file_path): logging.warning('Invalid path: %r', file_path) return None @@ -120,6 +136,7 @@ def get_bundle(metadata): logging.exception('Incorrect bundle') return None + def _get_activities_for_mime(mime_type): registry = bundleregistry.get_registry() result = registry.get_activities_for_type(mime_type) @@ -130,6 +147,7 @@ def _get_activities_for_mime(mime_type): result.append(activity) return result + def get_activities(metadata): activities = [] @@ -148,6 +166,7 @@ def get_activities(metadata): return activities + def resume(metadata, bundle_id=None): registry = bundleregistry.get_registry() @@ -159,19 +178,16 @@ def resume(metadata, bundle_id=None): bundle = ActivityBundle(file_path) if not registry.is_installed(bundle): logging.debug('Installing activity bundle') - registry.install(bundle) + try: + registry.install(bundle) + except AlreadyInstalledException: + _downgrade_option_alert(bundle) + return else: logging.debug('Upgrading activity bundle') registry.upgrade(bundle) - logging.debug('activityfactory.creating bundle with id %r', - bundle.get_bundle_id()) - installed_bundle = registry.get_bundle(bundle.get_bundle_id()) - if installed_bundle: - launch(installed_bundle) - else: - logging.error('Bundle %r is not installed.', - bundle.get_bundle_id()) + _launch_bundle(bundle) elif is_content_bundle(metadata) and bundle_id is None: @@ -206,7 +222,6 @@ def resume(metadata, bundle_id=None): bundle = registry.get_bundle(bundle_id) - if metadata.get('mountpoint', '/') == '/': object_id = metadata['uid'] else: @@ -215,6 +230,19 @@ def resume(metadata, bundle_id=None): launch(bundle, activity_id=activity_id, object_id=object_id, color=get_icon_color(metadata)) + +def _launch_bundle(bundle): + registry = bundleregistry.get_registry() + logging.debug('activityfactory.creating bundle with id %r', + bundle.get_bundle_id()) + installed_bundle = registry.get_bundle(bundle.get_bundle_id()) + if installed_bundle: + launch(installed_bundle) + else: + logging.error('Bundle %r is not installed.', + bundle.get_bundle_id()) + + def launch(bundle, activity_id=None, object_id=None, uri=None, color=None, invited=False): if activity_id is None or not activity_id: @@ -239,21 +267,46 @@ def launch(bundle, activity_id=None, object_id=None, uri=None, color=None, object_id=object_id, uri=uri, invited=invited) activityfactory.create(bundle, activity_handle) + +def _downgrade_option_alert(bundle): + alert = ConfirmationAlert() + alert.props.title = _('Older Version Of %s Activity') % (bundle.get_name()) + alert.props.msg = _('Do you want to downgrade to version %s') % \ + bundle.get_activity_version() + alert.connect('response', _downgrade_alert_response_cb, bundle) + journalwindow.get_journal_window().add_alert(alert) + alert.show() + + +def _downgrade_alert_response_cb(alert, response_id, bundle): + if response_id is gtk.RESPONSE_OK: + journalwindow.get_journal_window().remove_alert(alert) + registry = bundleregistry.get_registry() + registry.install(bundle, force_downgrade=True) + _launch_bundle(bundle) + elif response_id is gtk.RESPONSE_CANCEL: + journalwindow.get_journal_window().remove_alert(alert) + + def is_activity_bundle(metadata): mime_type = metadata.get('mime_type', '') return mime_type == ActivityBundle.MIME_TYPE or \ mime_type == ActivityBundle.DEPRECATED_MIME_TYPE + def is_content_bundle(metadata): return metadata.get('mime_type', '') == ContentBundle.MIME_TYPE + def is_journal_bundle(metadata): return metadata.get('mime_type', '') == JournalEntryBundle.MIME_TYPE + def is_bundle(metadata): return is_activity_bundle(metadata) or is_content_bundle(metadata) or \ is_journal_bundle(metadata) + def get_icon_color(metadata): if metadata is None or not 'icon-color' in metadata: client = gconf.client_get_default() diff --git a/src/jarabe/journal/modalalert.py b/src/jarabe/journal/modalalert.py index c7c6a0a..6880941 100644 --- a/src/jarabe/journal/modalalert.py +++ b/src/jarabe/journal/modalalert.py @@ -22,6 +22,7 @@ from sugar.graphics.icon import Icon from sugar.graphics import style from sugar.graphics.xocolor import XoColor + class ModalAlert(gtk.Window): __gtype_name__ = 'SugarModalAlert' @@ -84,14 +85,12 @@ class ModalAlert(gtk.Window): self.add(self._main_view) self._main_view.show() - self.connect("realize", self.__realize_cb) + self.connect('realize', self.__realize_cb) def __realize_cb(self, widget): self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) self.window.set_accept_focus(True) def __show_journal_cb(self, button): - '''The opener will listen on the destroy signal - ''' + """The opener will listen on the destroy signal""" self.destroy() - diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py index 81ca7d4..320e577 100644 --- a/src/jarabe/journal/model.py +++ b/src/jarabe/journal/model.py @@ -1,4 +1,4 @@ -# Copyright (C) 2007-2008, One Laptop Per Child +# Copyright (C) 2007-2010, One Laptop per Child # # 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 @@ -16,10 +16,11 @@ import logging import os +import errno from datetime import datetime import time import shutil -from stat import S_IFMT, S_IFDIR, S_IFREG +from stat import S_IFLNK, S_IFMT, S_IFDIR, S_IFREG import re from operator import itemgetter @@ -32,18 +33,25 @@ from sugar import dispatch from sugar import mime from sugar import util + DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore' DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore' DS_DBUS_PATH = '/org/laptop/sugar/DataStore' # Properties the journal cares about. -PROPERTIES = ['uid', 'title', 'mtime', 'timestamp', 'creation_time', 'filesize', - 'keep', 'buddies', 'icon-color', 'mime_type', 'progress', - 'activity', 'mountpoint', 'activity_id', 'bundle_id'] +PROPERTIES = ['activity', 'activity_id', 'buddies', 'bundle_id', + 'creation_time', 'filesize', 'icon-color', 'keep', 'mime_type', + 'mountpoint', 'mtime', 'progress', 'timestamp', 'title', 'uid'] MIN_PAGES_TO_CACHE = 3 MAX_PAGES_TO_CACHE = 5 +_datastore = None +created = dispatch.Signal() +updated = dispatch.Signal() +deleted = dispatch.Signal() + + class _Cache(object): __gtype_name__ = 'model_Cache' @@ -74,7 +82,7 @@ class BaseResultSet(object): """ def __init__(self, query, page_size): - self._total_count = -1 + self._total_count = -1 self._position = -1 self._query = query self._page_size = page_size @@ -141,7 +149,8 @@ class BaseResultSet(object): self._cache.append_all(entries) self._offset = offset - elif remaining_forward_entries <= 0 and remaining_backwards_entries > 0: + elif (remaining_forward_entries <= 0 and + remaining_backwards_entries > 0): # Add one page to the end of cache logging.debug('appending one more page, offset: %r', @@ -186,6 +195,7 @@ class BaseResultSet(object): return self._cache[self._position - self._offset] + class DatastoreResultSet(BaseResultSet): """Encapsulates the result of a query on the datastore """ @@ -213,6 +223,7 @@ class DatastoreResultSet(BaseResultSet): return entries, total_count + class InplaceResultSet(BaseResultSet): """Encapsulates the result of a query on a mount point """ @@ -220,7 +231,9 @@ class InplaceResultSet(BaseResultSet): BaseResultSet.__init__(self, query, page_size) self._mount_point = mount_point self._file_list = None - self._pending_directories = 0 + self._pending_directories = [] + self._visited_directories = [] + self._pending_files = [] self._stopped = False query_text = query.get('query', '') @@ -247,7 +260,10 @@ class InplaceResultSet(BaseResultSet): def setup(self): self._file_list = [] - self._recurse_dir(self._mount_point) + self._pending_directories = [self._mount_point] + self._visited_directories = [] + self._pending_files = [] + gobject.idle_add(self._scan) def stop(self): self._stopped = True @@ -256,10 +272,11 @@ class InplaceResultSet(BaseResultSet): if self._sort[1:] == 'filesize': keygetter = itemgetter(3) else: - keygetter = itemgetter(2) # timestamp - self._file_list.sort(lambda a, b: b - a, + # timestamp + keygetter = itemgetter(2) + self._file_list.sort(lambda a, b: cmp(b, a), key=keygetter, - reverse=self._sort[0]=='-') + reverse=(self._sort[0] == '-')) self.ready.send(self) def find(self, query): @@ -272,7 +289,7 @@ class InplaceResultSet(BaseResultSet): t = time.time() offset = int(query.get('offset', 0)) - limit = int(query.get('limit', len(self._file_list))) + limit = int(query.get('limit', len(self._file_list))) total_count = len(self._file_list) files = self._file_list[offset:offset + limit] @@ -287,62 +304,101 @@ class InplaceResultSet(BaseResultSet): return entries, total_count - def _recurse_dir(self, dir_path): - self._pending_directories += 1 - gobject.idle_add(self._idle_recurse_dir, dir_path) + def _scan(self): + if self._stopped: + return False - def _idle_recurse_dir(self, dir_path): - try: - self._real_recurse_dir(dir_path) - finally: - self._pending_directories -= 1 - if self._pending_directories == 0: - self.setup_ready() + self.progress.send(self) - def _real_recurse_dir(self, dir_path): - if self._stopped: - return + if self._pending_files: + self._scan_a_file() + return True + + if self._pending_directories: + self._scan_a_directory() + return True + + self.setup_ready() + self._visited_directories = [] + return False + + def _scan_a_file(self): + full_path = self._pending_files.pop(0) try: - dirs = os.listdir(dir_path) - except Exception: - logging.exception('Error reading directory %r', dir_path) - dirs = [] + stat = os.lstat(full_path) + except OSError, e: + if e.errno != errno.ENOENT: + logging.exception( + 'Error reading metadata of file %r', full_path) + return + + if S_IFMT(stat.st_mode) == S_IFLNK: + try: + link = os.readlink(full_path) + except OSError, e: + logging.exception( + 'Error reading target of link %r', full_path) + return + + if not os.path.abspath(link).startswith(self._mount_point): + return - for entry in dirs: - if entry.startswith('.'): - continue - full_path = dir_path + '/' + entry try: stat = os.stat(full_path) - if S_IFMT(stat.st_mode) == S_IFDIR: - self._recurse_dir(full_path) - elif S_IFMT(stat.st_mode) == S_IFREG: - add_to_list = True + except OSError, e: + if e.errno != errno.ENOENT: + logging.exception( + 'Error reading metadata of linked file %r', full_path) + return + + if S_IFMT(stat.st_mode) == S_IFDIR: + id_tuple = stat.st_ino, stat.st_dev + if not id_tuple in self._visited_directories: + self._visited_directories.append(id_tuple) + self._pending_directories.append(full_path) + return - if self._regex is not None and \ - not self._regex.match(full_path): - add_to_list = False + if S_IFMT(stat.st_mode) != S_IFREG: + return - if None not in [self._date_start, self._date_end] and \ - (stat.st_mtime < self._date_start or - stat.st_mtime > self._date_end): - add_to_list = False + if self._regex is not None and \ + not self._regex.match(full_path): + return - if self._mime_types: - mime_type = gio.content_type_guess(filename=full_path) - if mime_type not in self._mime_types: - add_to_list = False + if self._date_start is not None and stat.st_mtime < self._date_start: + return - if add_to_list: - file_info = (full_path, stat, int(stat.st_mtime), stat.st_size) - self._file_list.append(file_info) + if self._date_end is not None and stat.st_mtime > self._date_end: + return - self.progress.send(self) + if self._mime_types: + mime_type = gio.content_type_guess(filename=full_path) + if mime_type not in self._mime_types: + return + + file_info = (full_path, stat, int(stat.st_mtime), stat.st_size) + self._file_list.append(file_info) + + return + + def _scan_a_directory(self): + dir_path = self._pending_directories.pop(0) + + try: + entries = os.listdir(dir_path) + except OSError, e: + if e.errno != errno.EACCES: + logging.exception('Error reading directory %r', dir_path) + return + + for entry in entries: + if entry.startswith('.'): + continue + self._pending_files.append(dir_path + '/' + entry) + return - except Exception: - logging.exception('Error reading file %r', full_path) def _get_file_metadata(path, stat): client = gconf.client_get_default() @@ -356,7 +412,7 @@ def _get_file_metadata(path, stat): 'icon-color': client.get_string('/desktop/sugar/user/color'), 'description': path} -_datastore = None + def _get_datastore(): global _datastore if _datastore is None: @@ -370,15 +426,19 @@ def _get_datastore(): return _datastore + def _datastore_created_cb(object_id): created.send(None, object_id=object_id) + def _datastore_updated_cb(object_id): updated.send(None, object_id=object_id) + def _datastore_deleted_cb(object_id): deleted.send(None, object_id=object_id) + def find(query_, page_size): """Returns a ResultSet """ @@ -393,6 +453,7 @@ def find(query_, page_size): else: return InplaceResultSet(query, page_size, mount_points[0]) + def _get_mount_point(path): dir_path = os.path.dirname(path) while True: @@ -401,6 +462,7 @@ def _get_mount_point(path): else: dir_path = dir_path.rsplit(os.sep, 1)[0] + def get(object_id): """Returns the metadata for an object """ @@ -413,6 +475,7 @@ def get(object_id): metadata['mountpoint'] = '/' return metadata + def get_file(object_id): """Returns the file for an object """ @@ -427,6 +490,7 @@ def get_file(object_id): else: return None + def get_file_size(object_id): """Return the file size for an object """ @@ -442,12 +506,14 @@ def get_file_size(object_id): return 0 + def get_unique_values(key): """Returns a list with the different values a property has taken """ empty_dict = dbus.Dictionary({}, signature='ss') return _get_datastore().get_uniquevaluesfor(key, empty_dict) + def delete(object_id): """Removes an object from persistent storage """ @@ -457,6 +523,7 @@ def delete(object_id): else: _get_datastore().delete(object_id) + def copy(metadata, mount_point): """Copies an object to another mount point """ @@ -468,6 +535,7 @@ def copy(metadata, mount_point): return write(metadata, file_path, transfer_ownership=False) + def write(metadata, file_path='', update_mtime=True, transfer_ownership=True): """Creates or updates an entry for that id """ @@ -502,6 +570,7 @@ def write(metadata, file_path='', update_mtime=True, transfer_ownership=True): return object_id + def _get_file_name(title, mime_type): file_name = title @@ -526,11 +595,12 @@ def _get_file_name(title, mime_type): return file_name + def _get_unique_file_name(mount_point, file_name): if os.path.exists(os.path.join(mount_point, file_name)): i = 1 + name, extension = os.path.splitext(file_name) while len(file_name) <= 255: - name, extension = os.path.splitext(file_name) file_name = name + '_' + str(i) + extension if not os.path.exists(os.path.join(mount_point, file_name)): break @@ -538,10 +608,7 @@ def _get_unique_file_name(mount_point, file_name): return file_name + def is_editable(metadata): mountpoint = metadata.get('mountpoint', '/') return mountpoint == '/' - -created = dispatch.Signal() -updated = dispatch.Signal() -deleted = dispatch.Signal() diff --git a/src/jarabe/journal/objectchooser.py b/src/jarabe/journal/objectchooser.py index 16e6c4b..ecb8ecf 100644 --- a/src/jarabe/journal/objectchooser.py +++ b/src/jarabe/journal/objectchooser.py @@ -29,14 +29,13 @@ from jarabe.journal.listmodel import ListModel from jarabe.journal.journaltoolbox import SearchToolbar from jarabe.journal.volumestoolbar import VolumesToolbar + class ObjectChooser(gtk.Window): __gtype_name__ = 'ObjectChooser' __gsignals__ = { - 'response': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([int])) + 'response': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([int])), } def __init__(self, parent=None, what_filter=''): @@ -136,6 +135,7 @@ class ObjectChooser(gtk.Window): visible = event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED self._list_view.set_is_visible(visible) + class TitleBox(VolumesToolbar): __gtype_name__ = 'TitleBox' @@ -162,6 +162,7 @@ class TitleBox(VolumesToolbar): self.insert(tool_item, -1) tool_item.show() + class ChooserListView(BaseListView): __gtype_name__ = 'ChooserListView' @@ -196,4 +197,3 @@ class ChooserListView(BaseListView): self.emit('entry-activated', uid) return False - diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py index 7c3e5ff..9ae1afb 100644 --- a/src/jarabe/journal/palettes.py +++ b/src/jarabe/journal/palettes.py @@ -28,20 +28,19 @@ from sugar.graphics.icon import Icon from sugar.graphics.xocolor import XoColor from sugar import mime -from jarabe.model import bundleregistry from jarabe.model import friends from jarabe.model import filetransfer from jarabe.model import mimeregistry from jarabe.journal import misc from jarabe.journal import model + class ObjectPalette(Palette): __gtype_name__ = 'ObjectPalette' __gsignals__ = { - 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, + 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])), } @@ -54,7 +53,7 @@ class ObjectPalette(Palette): activity_icon.props.file = misc.get_icon_name(metadata) activity_icon.props.xo_color = misc.get_icon_color(metadata) - if metadata.has_key('title'): + if 'title' in metadata: title = gobject.markup_escape_text(metadata['title']) else: title = _('Untitled') @@ -62,22 +61,29 @@ class ObjectPalette(Palette): Palette.__init__(self, primary_text=title, icon=activity_icon) - if metadata.get('activity_id', ''): - resume_label = _('Resume') - resume_with_label = _('Resume with') - else: - resume_label = _('Start') - resume_with_label = _('Start with') - menu_item = MenuItem(resume_label, 'activity-start') - menu_item.connect('activate', self.__start_activate_cb) - self.menu.append(menu_item) - menu_item.show() + if misc.get_activities(metadata) or misc.is_bundle(metadata): + if metadata.get('activity_id', ''): + resume_label = _('Resume') + resume_with_label = _('Resume with') + else: + resume_label = _('Start') + resume_with_label = _('Start with') + menu_item = MenuItem(resume_label, 'activity-start') + menu_item.connect('activate', self.__start_activate_cb) + self.menu.append(menu_item) + menu_item.show() - menu_item = MenuItem(resume_with_label, 'activity-start') - self.menu.append(menu_item) - menu_item.show() - start_with_menu = StartWithMenu(self._metadata) - menu_item.set_submenu(start_with_menu) + menu_item = MenuItem(resume_with_label, 'activity-start') + self.menu.append(menu_item) + menu_item.show() + start_with_menu = StartWithMenu(self._metadata) + menu_item.set_submenu(start_with_menu) + + else: + menu_item = MenuItem(_('No activity to start entry')) + menu_item.set_sensitive(False) + self.menu.append(menu_item) + menu_item.show() client = gconf.client_get_default() color = XoColor(client.get_string('/desktop/sugar/user/color')) @@ -147,12 +153,13 @@ class ObjectPalette(Palette): filetransfer.start_transfer(buddy, file_name, title, description, mime_type) + class FriendsMenu(gtk.Menu): __gtype_name__ = 'JournalFriendsMenu' __gsignals__ = { - 'friend-selected' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([object])), + 'friend-selected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), } def __init__(self): diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py index 4208c17..2d842f1 100644 --- a/src/jarabe/journal/volumestoolbar.py +++ b/src/jarabe/journal/volumestoolbar.py @@ -16,6 +16,7 @@ import logging import os +import statvfs from gettext import gettext as _ import gobject @@ -26,20 +27,20 @@ import gconf from sugar.graphics.radiotoolbutton import RadioToolButton from sugar.graphics.palette import Palette from sugar.graphics.xocolor import XoColor +from sugar import env from jarabe.journal import model from jarabe.view.palettes import VolumePalette + class VolumesToolbar(gtk.Toolbar): __gtype_name__ = 'VolumesToolbar' __gsignals__ = { - 'volume-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, + 'volume-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])), - 'volume-error': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([str, str])) + 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str, str])), } def __init__(self): @@ -48,7 +49,6 @@ class VolumesToolbar(gtk.Toolbar): self._mount_removed_hid = None button = JournalButton() - button.set_palette(Palette(_('Journal'))) button.connect('toggled', self._button_toggled_cb) self.insert(button, 0) button.show() @@ -65,10 +65,10 @@ class VolumesToolbar(gtk.Toolbar): def _set_up_volumes(self): volume_monitor = gio.volume_monitor_get() - self._mount_added_hid = \ - volume_monitor.connect('mount-added', self.__mount_added_cb) - self._mount_removed_hid = \ - volume_monitor.connect('mount-removed', self.__mount_removed_cb) + self._mount_added_hid = volume_monitor.connect('mount-added', + self.__mount_added_cb) + self._mount_removed_hid = volume_monitor.connect('mount-removed', + self.__mount_removed_cb) for mount in volume_monitor.get_mounts(): self._add_button(mount) @@ -130,11 +130,11 @@ class VolumesToolbar(gtk.Toolbar): button = self._get_button_for_mount(mount) button.props.active = True + class BaseButton(RadioToolButton): __gsignals__ = { - 'volume-error': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([str, str])) + 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str, str])), } def __init__(self, mount_point): @@ -147,8 +147,8 @@ class BaseButton(RadioToolButton): gtk.gdk.ACTION_COPY) self.connect('drag-data-received', self._drag_data_received_cb) - def _drag_data_received_cb(self, widget, drag_context, x, y, selection_data, - info, timestamp): + def _drag_data_received_cb(self, widget, drag_context, x, y, + selection_data, info, timestamp): object_id = selection_data.data metadata = model.get(object_id) file_path = model.get_file(metadata['uid']) @@ -167,6 +167,7 @@ class BaseButton(RadioToolButton): _('Error while copying the entry. %s') % e.strerror, _('Error')) + class VolumeButton(BaseButton): def __init__(self, mount): self._mount = mount @@ -197,6 +198,7 @@ class VolumeButton(BaseButton): #palette.set_group_id('frame') return palette + class JournalButton(BaseButton): def __init__(self): BaseButton.__init__(self, mount_point='/') @@ -207,3 +209,36 @@ class JournalButton(BaseButton): color = XoColor(client.get_string('/desktop/sugar/user/color')) self.props.xo_color = color + def create_palette(self): + palette = JournalButtonPalette(self) + return palette + + +class JournalButtonPalette(Palette): + + def __init__(self, mount): + Palette.__init__(self, _('Journal')) + vbox = gtk.VBox() + self.set_content(vbox) + vbox.show() + + self._progress_bar = gtk.ProgressBar() + vbox.add(self._progress_bar) + self._progress_bar.show() + + self._free_space_label = gtk.Label() + self._free_space_label.set_alignment(0.5, 0.5) + vbox.add(self._free_space_label) + self._free_space_label.show() + + self.connect('popup', self.__popup_cb) + + def __popup_cb(self, palette): + stat = os.statvfs(env.get_profile_path()) + free_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL] + total_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BLOCKS] + + fraction = (total_space - free_space) / float(total_space) + self._progress_bar.props.fraction = fraction + self._free_space_label.props.label = _('%(free_space)d MB Free') % \ + {'free_space': free_space / (1024 * 1024)} diff --git a/src/jarabe/model/__init__.py b/src/jarabe/model/__init__.py index a9dd95a..85f6a24 100644 --- a/src/jarabe/model/__init__.py +++ b/src/jarabe/model/__init__.py @@ -13,4 +13,3 @@ # 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 - diff --git a/src/jarabe/model/adhoc.py b/src/jarabe/model/adhoc.py index ad0c941..8842a5c 100644 --- a/src/jarabe/model/adhoc.py +++ b/src/jarabe/model/adhoc.py @@ -24,6 +24,7 @@ from jarabe.model.network import Settings from sugar.util import unique_id from jarabe.model.network import IP4Config + _NM_SERVICE = 'org.freedesktop.NetworkManager' _NM_IFACE = 'org.freedesktop.NetworkManager' _NM_PATH = '/org/freedesktop/NetworkManager' @@ -32,8 +33,9 @@ _NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless' _NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' _NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' - _adhoc_manager_instance = None + + def get_adhoc_manager_instance(): global _adhoc_manager_instance if _adhoc_manager_instance is None: @@ -53,7 +55,7 @@ class AdHocManager(gobject.GObject): 'members-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), 'state-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])) + ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), } _AUTOCONNECT_TIMEOUT = 30 @@ -82,7 +84,7 @@ class AdHocManager(gobject.GObject): ' only be called once.') self._device = device - props = dbus.Interface(device, 'org.freedesktop.DBus.Properties') + props = dbus.Interface(device, dbus.PROPERTIES_IFACE) self._device_state = props.Get(_NM_DEVICE_IFACE, 'State') self._bus.add_signal_receiver(self.__device_state_changed_cb, @@ -158,7 +160,7 @@ class AdHocManager(gobject.GObject): def __idle_check_cb(self): if self._device_state == network.DEVICE_STATE_DISCONNECTED: - logging.debug("Connect to Ad-hoc network due to inactivity.") + logging.debug('Connect to Ad-hoc network due to inactivity.') self._autoconnect_adhoc() return False @@ -186,7 +188,7 @@ class AdHocManager(gobject.GObject): self._connect(channel) def _connect(self, channel): - name = "Ad-hoc Network %d" % channel + name = 'Ad-hoc Network %d' % channel connection = network.find_connection_by_ssid(name) if connection is None: settings = Settings() diff --git a/src/jarabe/model/buddy.py b/src/jarabe/model/buddy.py index 5f3176e..c580e68 100644 --- a/src/jarabe/model/buddy.py +++ b/src/jarabe/model/buddy.py @@ -28,8 +28,12 @@ from sugar.profile import get_profile from jarabe.util.telepathy import connection_watcher + CONNECTION_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo' +_owner_instance = None + + class BaseBuddyModel(gobject.GObject): __gtype_name__ = 'SugarBaseBuddyModel' @@ -89,6 +93,7 @@ class BaseBuddyModel(gobject.GObject): class OwnerBuddyModel(BaseBuddyModel): __gtype_name__ = 'SugarOwnerBuddyModel' + def __init__(self): BaseBuddyModel.__init__(self) @@ -180,7 +185,6 @@ class OwnerBuddyModel(BaseBuddyModel): return True -_owner_instance = None def get_owner_instance(): global _owner_instance if _owner_instance is None: @@ -190,6 +194,7 @@ def get_owner_instance(): class BuddyModel(BaseBuddyModel): __gtype_name__ = 'SugarBuddyModel' + def __init__(self, **kwargs): self._account = None @@ -225,4 +230,5 @@ class BuddyModel(BaseBuddyModel): def set_handle(self, handle): self._handle = handle - handle = gobject.property(type=object, getter=get_handle, setter=set_handle) + handle = gobject.property(type=object, getter=get_handle, + setter=set_handle) diff --git a/src/jarabe/model/bundleregistry.py b/src/jarabe/model/bundleregistry.py index 699e339..84d55c0 100644 --- a/src/jarabe/model/bundleregistry.py +++ b/src/jarabe/model/bundleregistry.py @@ -17,7 +17,6 @@ import os import logging -import traceback import gconf import gobject @@ -26,6 +25,7 @@ import simplejson from sugar.bundle.activitybundle import ActivityBundle from sugar.bundle.contentbundle import ContentBundle +from sugar.bundle.bundleversion import NormalizedVersion from jarabe.journal.journalentrybundle import JournalEntryBundle from sugar.bundle.bundle import MalformedBundleException, \ AlreadyInstalledException, RegistrationException @@ -34,16 +34,20 @@ from sugar import env from jarabe import config from jarabe.model import mimeregistry + +_instance = None + + class BundleRegistry(gobject.GObject): """Tracks the available activity bundles""" __gsignals__ = { - 'bundle-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), + 'bundle-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), 'bundle-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), 'bundle-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) + ([gobject.TYPE_PYOBJECT])), } def __init__(self): @@ -153,14 +157,16 @@ class BundleRegistry(gobject.GObject): return for bundle_id in default_activities: - max_version = -1 + max_version = '0' for bundle in self._bundles: if bundle.get_bundle_id() == bundle_id and \ - max_version < bundle.get_activity_version(): + NormalizedVersion(max_version) < \ + NormalizedVersion(bundle.get_activity_version()): max_version = bundle.get_activity_version() key = self._get_favorite_key(bundle_id, max_version) - if max_version > -1 and key not in self._favorite_bundles: + if NormalizedVersion(max_version) > NormalizedVersion('0') and \ + key not in self._favorite_bundles: self._favorite_bundles[key] = None logging.debug('After merging: %r', self._favorite_bundles) @@ -168,20 +174,10 @@ class BundleRegistry(gobject.GObject): self._write_favorites_file() def get_bundle(self, bundle_id): - """Returns a bundle given service name or substring, - returns None if there is either no match, or more than one - match by substring.""" - result = [] - key = bundle_id.lower() - + """Returns an bundle given his service name""" for bundle in self._bundles: - name = bundle.get_bundle_id() - if name == bundle_id: + if bundle.get_bundle_id() == bundle_id: return bundle - if key in name.lower(): - result.append(bundle) - if len(result) == 1: - return result[0] return None def __iter__(self): @@ -204,19 +200,18 @@ class BundleRegistry(gobject.GObject): if os.path.isdir(bundle_dir): bundles[bundle_dir] = os.stat(bundle_dir).st_mtime except Exception: - logging.error('Error while processing installed activity ' \ - 'bundle %s:\n%s' % \ - (bundle_dir, traceback.format_exc())) + logging.exception('Error while processing installed activity' + ' bundle %s:', bundle_dir) bundle_dirs = bundles.keys() bundle_dirs.sort(lambda d1, d2: cmp(bundles[d1], bundles[d2])) for folder in bundle_dirs: try: self._add_bundle(folder) - except Exception, e: - logging.error('Error while processing installed activity ' \ - 'bundle %s:\n%s' % \ - (folder, traceback.format_exc())) + except: + # pylint: disable=W0702 + logging.exception('Error while processing installed activity' + ' bundle %s:', folder) def add_bundle(self, bundle_path, install_mime_type=False): bundle = self._add_bundle(bundle_path, install_mime_type) @@ -243,8 +238,8 @@ class BundleRegistry(gobject.GObject): installed = self.get_bundle(bundle_id) if installed is not None: - if installed.get_activity_version() >= \ - bundle.get_activity_version(): + if NormalizedVersion(installed.get_activity_version()) >= \ + NormalizedVersion(bundle.get_activity_version()): logging.debug('Skip old version for %s', bundle_id) return None else: @@ -270,8 +265,7 @@ class BundleRegistry(gobject.GObject): default_bundle = None for bundle in self._bundles: - if bundle.get_mime_types() and mime_type in bundle.get_mime_types(): - + if mime_type in (bundle.get_mime_types() or []): if bundle.get_bundle_id() == default_bundle_id: default_bundle = bundle elif self.get_default_for_type(mime_type) == \ @@ -286,10 +280,7 @@ class BundleRegistry(gobject.GObject): return result def get_default_for_type(self, mime_type): - if self._mime_defaults.has_key(mime_type): - return self._mime_defaults[mime_type] - else: - return None + return self._mime_defaults.get(mime_type) def _find_bundle(self, bundle_id, version): for bundle in self._bundles: @@ -327,7 +318,8 @@ class BundleRegistry(gobject.GObject): def set_bundle_position(self, bundle_id, version, x, y): key = self._get_favorite_key(bundle_id, version) if key not in self._favorite_bundles: - raise ValueError('Bundle %s %s not favorite' % (bundle_id, version)) + raise ValueError('Bundle %s %s not favorite' % + (bundle_id, version)) if self._favorite_bundles[key] is None: self._favorite_bundles[key] = {} @@ -368,19 +360,22 @@ class BundleRegistry(gobject.GObject): for installed_bundle in self._bundles: if bundle.get_bundle_id() == installed_bundle.get_bundle_id() and \ - bundle.get_activity_version() == \ - installed_bundle.get_activity_version(): + NormalizedVersion(bundle.get_activity_version()) <= \ + NormalizedVersion(installed_bundle.get_activity_version()): return True return False - def install(self, bundle, uid=None): + def install(self, bundle, uid=None, force_downgrade=False): activities_path = env.get_user_activities_path() for installed_bundle in self._bundles: if bundle.get_bundle_id() == installed_bundle.get_bundle_id() and \ - bundle.get_activity_version() <= \ - installed_bundle.get_activity_version(): - raise AlreadyInstalledException + NormalizedVersion(bundle.get_activity_version()) <= \ + NormalizedVersion(installed_bundle.get_activity_version()): + if not force_downgrade: + raise AlreadyInstalledException + else: + self.uninstall(installed_bundle, force=True) elif bundle.get_bundle_id() == installed_bundle.get_bundle_id(): self.uninstall(installed_bundle, force=True) @@ -437,20 +432,17 @@ class BundleRegistry(gobject.GObject): try: self.uninstall(bundle, force=True) except Exception: - logging.error('Uninstall failed, still trying to install ' \ - 'newer bundle:\n' + \ - traceback.format_exc()) + logging.exception('Uninstall failed, still trying to install' + ' newer bundle:') else: - logging.warning('Unable to uninstall system activity, ' \ + logging.warning('Unable to uninstall system activity, ' 'installing upgraded version in user activities') self.install(bundle) -_instance = None def get_registry(): global _instance if not _instance: _instance = BundleRegistry() return _instance - diff --git a/src/jarabe/model/filetransfer.py b/src/jarabe/model/filetransfer.py index 0d21793..710c3a4 100644 --- a/src/jarabe/model/filetransfer.py +++ b/src/jarabe/model/filetransfer.py @@ -33,6 +33,7 @@ from sugar import dispatch from jarabe.util.telepathy import connection_watcher from jarabe.model import neighborhood + FT_STATE_NONE = 0 FT_STATE_PENDING = 1 FT_STATE_ACCEPTED = 2 @@ -52,14 +53,18 @@ FT_REASON_REMOTE_ERROR = 6 CHANNEL_TYPE_FILE_TRANSFER = \ 'org.freedesktop.Telepathy.Channel.Type.FileTransfer' +new_file_transfer = dispatch.Signal() + + # TODO Move to use splice_async() in Sugar 0.88 class StreamSplicer(gobject.GObject): - _CHUNK_SIZE = 10240 # 10K + _CHUNK_SIZE = 10240 # 10K __gsignals__ = { 'finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } + def __init__(self, input_stream, output_stream): gobject.GObject.__init__(self) @@ -105,6 +110,7 @@ class StreamSplicer(gobject.GObject): gobject.PRIORITY_LOW, user_data=data) + class BaseFileTransfer(gobject.GObject): def __init__(self, connection): @@ -176,6 +182,7 @@ class BaseFileTransfer(gobject.GObject): def cancel(self): self.channel[CHANNEL].Close() + class IncomingFileTransfer(BaseFileTransfer): def __init__(self, connection, object_path, props): BaseFileTransfer.__init__(self, connection) @@ -220,6 +227,7 @@ class IncomingFileTransfer(BaseFileTransfer): self._splicer = StreamSplicer(input_stream, output_stream) self._splicer.start() + class OutgoingFileTransfer(BaseFileTransfer): def __init__(self, buddy, file_name, title, description, mime_type): @@ -283,6 +291,7 @@ class OutgoingFileTransfer(BaseFileTransfer): def cancel(self): self.channel[CHANNEL].Close() + def _new_channels_cb(connection, channels): for object_path, props in channels: if props[CHANNEL + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER and \ @@ -294,17 +303,21 @@ def _new_channels_cb(connection, channels): object_path, props) new_file_transfer.send(None, file_transfer=incoming_file_transfer) + def _monitor_connection(connection): logging.debug('connection added %r', connection) connection[CONNECTION_INTERFACE_REQUESTS].connect_to_signal('NewChannels', lambda channels: _new_channels_cb(connection, channels)) + def _connection_added_cb(conn_watcher, connection): _monitor_connection(connection) + def _connection_removed_cb(conn_watcher, connection): logging.debug('connection removed %r', connection) + def init(): conn_watcher = connection_watcher.get_instance() conn_watcher.connect('connection-added', _connection_added_cb) @@ -313,11 +326,13 @@ def init(): for connection in conn_watcher.get_connections(): _monitor_connection(connection) + def start_transfer(buddy, file_name, title, description, mime_type): outgoing_file_transfer = OutgoingFileTransfer(buddy, file_name, title, description, mime_type) new_file_transfer.send(None, file_transfer=outgoing_file_transfer) + def file_transfer_available(): conn_watcher = connection_watcher.get_instance() for connection in conn_watcher.get_connections(): @@ -337,18 +352,17 @@ def file_transfer_available(): return False -new_file_transfer = dispatch.Signal() if __name__ == '__main__': import tempfile - input_stream = gio.File('/home/tomeu/isos/Soas2-200904031934.iso').read() - output_stream = gio.File(tempfile.mkstemp()[1]).append_to() + test_file_name = '/home/tomeu/isos/Soas2-200904031934.iso' + test_input_stream = gio.File(test_file_name).read() + test_output_stream = gio.File(tempfile.mkstemp()[1]).append_to() # TODO: Use splice_async when it gets implemented - splicer = StreamSplicer(input_stream, output_stream) + splicer = StreamSplicer(test_input_stream, test_output_stream) splicer.start() loop = gobject.MainLoop() loop.run() - diff --git a/src/jarabe/model/friends.py b/src/jarabe/model/friends.py index 98bff96..192f683 100644 --- a/src/jarabe/model/friends.py +++ b/src/jarabe/model/friends.py @@ -27,10 +27,14 @@ from sugar.graphics.xocolor import XoColor from jarabe.model.buddy import BuddyModel from jarabe.model import neighborhood + +_model = None + + class FriendBuddyModel(BuddyModel): __gtype_name__ = 'SugarFriendBuddyModel' - _NOT_PRESENT_COLOR = "#D5D5D5,#FFFFFF" + _NOT_PRESENT_COLOR = '#D5D5D5,#FFFFFF' def __init__(self, nick, key): self._online_buddy = None @@ -45,7 +49,7 @@ class FriendBuddyModel(BuddyModel): if buddy is not None: self._set_online_buddy(buddy) - def __buddy_added_cb(self, neighborhood, buddy): + def __buddy_added_cb(self, model_, buddy): if buddy.key != self.key: return self._set_online_buddy(buddy) @@ -56,7 +60,7 @@ class FriendBuddyModel(BuddyModel): self.notify('color') self.notify('present') - def __buddy_removed_cb(self, neighborhood, buddy): + def __buddy_removed_cb(self, model_, buddy): if buddy.key != self.key: return self._online_buddy = None @@ -87,12 +91,13 @@ class FriendBuddyModel(BuddyModel): handle = gobject.property(type=object, getter=get_handle) + class Friends(gobject.GObject): __gsignals__ = { - 'friend-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])), - 'friend-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([str])) + 'friend-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'friend-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str])), } def __init__(self): @@ -104,7 +109,7 @@ class Friends(gobject.GObject): self.load() def has_buddy(self, buddy): - return self._friends.has_key(buddy.get_key()) + return buddy.get_key() in self._friends def add_friend(self, buddy_info): self._friends[buddy_info.get_key()] = buddy_info @@ -176,7 +181,6 @@ class Friends(gobject.GObject): reply_handler=friends_synced, error_handler=friends_synced_error) -_model = None def get_model(): global _model diff --git a/src/jarabe/model/invites.py b/src/jarabe/model/invites.py index e5a4d9d..d2a2e0c 100644 --- a/src/jarabe/model/invites.py +++ b/src/jarabe/model/invites.py @@ -38,9 +38,12 @@ from jarabe.model import bundleregistry from jarabe.model import neighborhood from jarabe.journal import misc + CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \ 'org.laptop.Telepathy.ActivityProperties' +_instance = None + class ActivityInvite(object): """Invitation to a shared activity.""" @@ -96,8 +99,8 @@ class ActivityInvite(object): obj = bus.get_object(CHANNEL_DISPATCHER, self.dispatch_operation_path) dispatch_operation = dbus.Interface(obj, CHANNEL_DISPATCH_OPERATION) dispatch_operation.HandleWith(self._handler, - reply_handler=self.__handle_with_reply_cb, - error_handler=self.__handle_with_reply_cb) + reply_handler=self.__handle_with_reply_cb, + error_handler=self.__handle_with_reply_cb) def __handle_with_reply_cb(self, error=None): if error is not None: @@ -105,12 +108,13 @@ class ActivityInvite(object): else: logging.debug('__handle_with_reply_cb') + class Invites(gobject.GObject): __gsignals__ = { - 'invite-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])), - 'invite-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])) + 'invite-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'invite-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), } def __init__(self): @@ -199,8 +203,8 @@ class Invites(gobject.GObject): obj = bus.get_object(CHANNEL_DISPATCHER, dispatch_operation_path) dispatch_operation = dbus.Interface(obj, CHANNEL_DISPATCH_OPERATION) dispatch_operation.HandleWith(handler, - reply_handler=self.__handle_with_reply_cb, - error_handler=self.__handle_with_reply_cb) + reply_handler=self.__handle_with_reply_cb, + error_handler=self.__handle_with_reply_cb) def __handle_with_reply_cb(self, error=None): if error is not None: @@ -230,8 +234,6 @@ class Invites(gobject.GObject): return self._dispatch_operations.values().__iter__() -_instance = None - def get_instance(): global _instance if not _instance: diff --git a/src/jarabe/model/mimeregistry.py b/src/jarabe/model/mimeregistry.py index 537f6f3..7fb5bcf 100644 --- a/src/jarabe/model/mimeregistry.py +++ b/src/jarabe/model/mimeregistry.py @@ -21,6 +21,7 @@ import gconf _DEFAULTS_KEY = '/desktop/sugar/journal/defaults' _GCONF_INVALID_CHARS = re.compile('[^a-zA-Z0-9-_/.]') + _instance = None diff --git a/src/jarabe/model/neighborhood.py b/src/jarabe/model/neighborhood.py index 91dd059..ca4c5bf 100644 --- a/src/jarabe/model/neighborhood.py +++ b/src/jarabe/model/neighborhood.py @@ -48,6 +48,7 @@ from sugar.profile import get_profile from jarabe.model.buddy import BuddyModel, get_owner_instance from jarabe.model import bundleregistry + ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager' ACCOUNT_MANAGER_PATH = '/org/freedesktop/Telepathy/AccountManager' CHANNEL_DISPATCHER_SERVICE = 'org.freedesktop.Telepathy.ChannelDispatcher' @@ -65,17 +66,21 @@ Time in seconds to wait when querying contact properties. Some jabber servers will be very slow in returning these queries, so just be patient. """ +_model = None + + class ActivityModel(gobject.GObject): __gsignals__ = { - 'current-buddy-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])), - 'current-buddy-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])), - 'buddy-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])), - 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])), + 'current-buddy-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'current-buddy-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'buddy-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), } + def __init__(self, activity_id, room_handle): gobject.GObject.__init__(self) @@ -102,7 +107,8 @@ class ActivityModel(gobject.GObject): def set_bundle(self, bundle): self._bundle = bundle - bundle = gobject.property(type=object, getter=get_bundle, setter=set_bundle) + bundle = gobject.property(type=object, getter=get_bundle, + setter=set_bundle) def get_name(self): return self._name @@ -151,30 +157,29 @@ class ActivityModel(gobject.GObject): current_buddies = gobject.property(type=object, getter=get_current_buddies) + class _Account(gobject.GObject): __gsignals__ = { - 'activity-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object, object])), - 'activity-updated': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object, object])), - 'activity-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])), - 'buddy-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object, object, object])), - 'buddy-updated': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object, object])), - 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])), - 'buddy-joined-activity': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object, object])), - 'buddy-left-activity': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object, object])), + 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object, object])), + 'activity-updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object, object])), + 'activity-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'buddy-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object, object, object])), + 'buddy-updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object, object])), + 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'buddy-joined-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object, object])), + 'buddy-left-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object, object])), 'current-activity-updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([object, object])), - 'connected': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), - 'disconnected': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), + 'connected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'disconnected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } def __init__(self, account_path): @@ -203,7 +208,8 @@ class _Account(gobject.GObject): 'AccountPropertyChanged', self.__account_property_changed_cb) def __error_handler_cb(self, function_name, error): - raise RuntimeError('Error when calling %s: %s' % (function_name, error)) + raise RuntimeError('Error when calling %s: %s' % (function_name, + error)) def __got_connection_cb(self, connection_path): logging.debug('_Account.__got_connection_cb %r', connection_path) @@ -379,7 +385,8 @@ class _Account(gobject.GObject): logging.debug('Got handle %r with nick %r, going to update', handle, alias) properties = {CONNECTION_INTERFACE_ALIASING + '/alias': alias} - self.emit('buddy-updated', self._buddy_handles[handle], properties) + self.emit('buddy-updated', self._buddy_handles[handle], + properties) def __presences_changed_cb(self, presences): logging.debug('_Account.__presences_changed_cb %r', presences) @@ -573,7 +580,8 @@ class _Account(gobject.GObject): connection.GetActivities( handle, - reply_handler=partial(self.__got_activities_cb, handle), + reply_handler=partial(self.__got_activities_cb, + handle), error_handler=partial(self.__error_handler_cb, 'BuddyInfo.GetActivities'), timeout=_QUERY_DBUS_TIMEOUT) @@ -609,21 +617,22 @@ class _Account(gobject.GObject): reply_handler=self.__set_enabled_cb, error_handler=partial(self.__error_handler_cb, 'Account.SetEnabled'), - dbus_interface='org.freedesktop.DBus.Properties') + dbus_interface=dbus.PROPERTIES_IFACE) def __set_enabled_cb(self): logging.debug('_Account.__set_enabled_cb success') + class Neighborhood(gobject.GObject): __gsignals__ = { - 'activity-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])), - 'activity-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])), - 'buddy-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])), - 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])) + 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'activity-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'buddy-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), } def __init__(self): @@ -635,7 +644,8 @@ class Neighborhood(gobject.GObject): self._server_account = None client = gconf.client_get_default() - client.add_dir('/desktop/sugar/collaboration', gconf.CLIENT_PRELOAD_NONE) + client.add_dir('/desktop/sugar/collaboration', + gconf.CLIENT_PRELOAD_NONE) client.notify_add('/desktop/sugar/collaboration/jabber_server', self.__jabber_server_changed_cb) client.add_dir('/desktop/sugar/user/nick', gconf.CLIENT_PRELOAD_NONE) @@ -732,7 +742,8 @@ class Neighborhood(gobject.GObject): client = gconf.client_get_default() nick = client.get_string('/desktop/sugar/user/nick') - server = client.get_string('/desktop/sugar/collaboration/jabber_server') + server = client.get_string('/desktop/sugar/collaboration' + '/jabber_server') key_hash = get_profile().privkey_hash params = { @@ -764,7 +775,8 @@ class Neighborhood(gobject.GObject): def _get_jabber_account_id(self): public_key_hash = sha1(get_profile().pubkey).hexdigest() client = gconf.client_get_default() - server = client.get_string('/desktop/sugar/collaboration/jabber_server') + server = client.get_string('/desktop/sugar/collaboration' + '/jabber_server') return '%s@%s' % (public_key_hash, server) def __jabber_server_changed_cb(self, client, timestamp, entry, *extra): @@ -774,7 +786,8 @@ class Neighborhood(gobject.GObject): account = bus.get_object(ACCOUNT_MANAGER_SERVICE, self._server_account.object_path) - server = client.get_string('/desktop/sugar/collaboration/jabber_server') + server = client.get_string( + '/desktop/sugar/collaboration/jabber_server') account_id = self._get_jabber_account_id() needs_reconnect = account.UpdateParameters({'server': server, 'account': account_id, @@ -830,8 +843,8 @@ class Neighborhood(gobject.GObject): return if contact_id not in self._buddies: - logging.debug('__buddy_updated_cb Unknown buddy with contact_id %r', - contact_id) + logging.debug('__buddy_updated_cb Unknown buddy with contact_id' + ' %r', contact_id) return buddy = self._buddies[contact_id] @@ -917,8 +930,8 @@ class Neighborhood(gobject.GObject): 'contact_id %r', contact_id) return if activity_id and activity_id not in self._activities: - logging.debug('__current_activity_updated_cb Unknown activity with ' - 'id %s', activity_id) + logging.debug('__current_activity_updated_cb Unknown activity with' + ' id %s', activity_id) activity_id = '' buddy = self._buddies[contact_id] @@ -987,7 +1000,6 @@ class Neighborhood(gobject.GObject): def get_activities(self): return self._activities.values() -_model = None def get_model(): global _model diff --git a/src/jarabe/model/network.py b/src/jarabe/model/network.py index 79e4360..f265ae4 100644 --- a/src/jarabe/model/network.py +++ b/src/jarabe/model/network.py @@ -34,6 +34,7 @@ from sugar import dispatch from sugar import env from sugar.util import unique_id + DEVICE_TYPE_802_3_ETHERNET = 1 DEVICE_TYPE_802_11_WIRELESS = 2 DEVICE_TYPE_GSM_MODEM = 3 @@ -129,11 +130,16 @@ NM_802_11_DEVICE_CAP_RSN = 0x00000020 SETTINGS_SERVICE = 'org.freedesktop.NetworkManagerUserSettings' +NM_SERVICE = 'org.freedesktop.NetworkManager' +NM_IFACE = 'org.freedesktop.NetworkManager' +NM_PATH = '/org/freedesktop/NetworkManager' +NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' NM_SETTINGS_PATH = '/org/freedesktop/NetworkManagerSettings' NM_SETTINGS_IFACE = 'org.freedesktop.NetworkManagerSettings' NM_CONNECTION_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection' NM_SECRETS_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection.Secrets' NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' +NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' GSM_USERNAME_PATH = '/desktop/sugar/network/gsm/username' GSM_PASSWORD_PATH = '/desktop/sugar/network/gsm/password' @@ -147,102 +153,102 @@ _conn_counter = 0 _nm_device_state_reason_description = None + def get_error_by_reason(reason): global _nm_device_state_reason_description if _nm_device_state_reason_description is None: _nm_device_state_reason_description = { - NM_DEVICE_STATE_REASON_UNKNOWN: \ - _("The reason for the device state change \ is unknown."), - NM_DEVICE_STATE_REASON_NONE: \ - _("The state change is normal."), - NM_DEVICE_STATE_REASON_NOW_MANAGED: \ - _("The device is now managed."), - NM_DEVICE_STATE_REASON_NOW_UNMANAGED: \ - _("The device is no longer managed."), - NM_DEVICE_STATE_REASON_CONFIG_FAILED: \ - _("The device could not be readied for configuration."), - NM_DEVICE_STATE_REASON_CONFIG_UNAVAILABLE : \ - _("IP configuration could not be reserved " \ - "(no available address, timeout, etc)."), - NM_DEVICE_STATE_REASON_CONFIG_EXPIRED : \ - _("The IP configuration is no longer valid."), - NM_DEVICE_STATE_REASON_NO_SECRETS : \ - _("Secrets were required, but not provided."), - NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT : \ - _("The 802.1X supplicant disconnected from "\ - "the access point or authentication server."), - NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED : \ - _("Configuration of the 802.1X supplicant failed."), - NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED : \ - _("The 802.1X supplicant quit or failed unexpectedly."), - NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT : \ - _("The 802.1X supplicant took too long to authenticate."), - NM_DEVICE_STATE_REASON_PPP_START_FAILED : \ - _("The PPP service failed to start within the allowed time."), - NM_DEVICE_STATE_REASON_PPP_DISCONNECT : \ - _("The PPP service disconnected unexpectedly."), - NM_DEVICE_STATE_REASON_PPP_FAILED : \ - _("The PPP service quit or failed unexpectedly."), - NM_DEVICE_STATE_REASON_DHCP_START_FAILED : \ - _("The DHCP service failed to start within the allowed time."), - NM_DEVICE_STATE_REASON_DHCP_ERROR : \ - _("The DHCP service reported an unexpected error."), - NM_DEVICE_STATE_REASON_DHCP_FAILED : \ - _("The DHCP service quit or failed unexpectedly."), - NM_DEVICE_STATE_REASON_SHARED_START_FAILED : \ - _("The shared connection service failed to start."), - NM_DEVICE_STATE_REASON_SHARED_FAILED : \ - _("The shared connection service quit or " \ - "failed unexpectedly."), - NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED : \ - _("The AutoIP service failed to start."), - NM_DEVICE_STATE_REASON_AUTOIP_ERROR : \ - _("The AutoIP service reported an unexpected error."), - NM_DEVICE_STATE_REASON_AUTOIP_FAILED : \ - _("The AutoIP service quit or failed unexpectedly."), - NM_DEVICE_STATE_REASON_MODEM_BUSY : \ - _("Dialing failed because the line was busy."), - NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE : \ - _("Dialing failed because there was no dial tone."), - NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER : \ - _("Dialing failed because there was no carrier."), - NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT : \ - _("Dialing timed out."), - NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED : \ - _("Dialing failed."), - NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED : \ - _("Modem initialization failed."), - NM_DEVICE_STATE_REASON_GSM_APN_FAILED : \ - _("Failed to select the specified GSM APN."), - NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING : \ - _("Not searching for networks."), - NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED : \ - _("Network registration was denied."), - NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT : \ - _("Network registration timed out."), - NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED : \ - _("Failed to register with the requested GSM network."), - NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED : \ - _("PIN check failed."), - NM_DEVICE_STATE_REASON_FIRMWARE_MISSING : \ - _("Necessary firmware for the device may be missing."), - NM_DEVICE_STATE_REASON_REMOVED : \ - _("The device was removed."), - NM_DEVICE_STATE_REASON_SLEEPING : \ - _("NetworkManager went to sleep."), - NM_DEVICE_STATE_REASON_CONNECTION_REMOVED : \ - _("The device's active connection was removed " \ - "or disappeared."), - NM_DEVICE_STATE_REASON_USER_REQUESTED : \ - _("A user or client requested the disconnection."), - NM_DEVICE_STATE_REASON_CARRIER : \ + NM_DEVICE_STATE_REASON_UNKNOWN: + _('The reason for the device state change is unknown.'), + NM_DEVICE_STATE_REASON_NONE: + _('The state change is normal.'), + NM_DEVICE_STATE_REASON_NOW_MANAGED: + _('The device is now managed.'), + NM_DEVICE_STATE_REASON_NOW_UNMANAGED: + _('The device is no longer managed.'), + NM_DEVICE_STATE_REASON_CONFIG_FAILED: + _('The device could not be readied for configuration.'), + NM_DEVICE_STATE_REASON_CONFIG_UNAVAILABLE: + _('IP configuration could not be reserved ' + '(no available address, timeout, etc).'), + NM_DEVICE_STATE_REASON_CONFIG_EXPIRED: + _('The IP configuration is no longer valid.'), + NM_DEVICE_STATE_REASON_NO_SECRETS: + _('Secrets were required, but not provided.'), + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT: + _('The 802.1X supplicant disconnected from ' + 'the access point or authentication server.'), + NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED: + _('Configuration of the 802.1X supplicant failed.'), + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED: + _('The 802.1X supplicant quit or failed unexpectedly.'), + NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT: + _('The 802.1X supplicant took too long to authenticate.'), + NM_DEVICE_STATE_REASON_PPP_START_FAILED: + _('The PPP service failed to start within the allowed time.'), + NM_DEVICE_STATE_REASON_PPP_DISCONNECT: + _('The PPP service disconnected unexpectedly.'), + NM_DEVICE_STATE_REASON_PPP_FAILED: + _('The PPP service quit or failed unexpectedly.'), + NM_DEVICE_STATE_REASON_DHCP_START_FAILED: + _('The DHCP service failed to start within the allowed time.'), + NM_DEVICE_STATE_REASON_DHCP_ERROR: + _('The DHCP service reported an unexpected error.'), + NM_DEVICE_STATE_REASON_DHCP_FAILED: + _('The DHCP service quit or failed unexpectedly.'), + NM_DEVICE_STATE_REASON_SHARED_START_FAILED: + _('The shared connection service failed to start.'), + NM_DEVICE_STATE_REASON_SHARED_FAILED: + _('The shared connection service quit or failed' + ' unexpectedly.'), + NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED: + _('The AutoIP service failed to start.'), + NM_DEVICE_STATE_REASON_AUTOIP_ERROR: + _('The AutoIP service reported an unexpected error.'), + NM_DEVICE_STATE_REASON_AUTOIP_FAILED: + _('The AutoIP service quit or failed unexpectedly.'), + NM_DEVICE_STATE_REASON_MODEM_BUSY: + _('Dialing failed because the line was busy.'), + NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE: + _('Dialing failed because there was no dial tone.'), + NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER: + _('Dialing failed because there was no carrier.'), + NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT: + _('Dialing timed out.'), + NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED: + _('Dialing failed.'), + NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED: + _('Modem initialization failed.'), + NM_DEVICE_STATE_REASON_GSM_APN_FAILED: + _('Failed to select the specified GSM APN'), + NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING: + _('Not searching for networks.'), + NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED: + _('Network registration was denied.'), + NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT: + _('Network registration timed out.'), + NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED: + _('Failed to register with the requested GSM network.'), + NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED: + _('PIN check failed.'), + NM_DEVICE_STATE_REASON_FIRMWARE_MISSING: + _('Necessary firmware for the device may be missing.'), + NM_DEVICE_STATE_REASON_REMOVED: + _('The device was removed.'), + NM_DEVICE_STATE_REASON_SLEEPING: + _('NetworkManager went to sleep.'), + NM_DEVICE_STATE_REASON_CONNECTION_REMOVED: + _("The device's active connection was removed " + "or disappeared."), + NM_DEVICE_STATE_REASON_USER_REQUESTED: + _('A user or client requested the disconnection.'), + NM_DEVICE_STATE_REASON_CARRIER: _("The device's carrier/link changed.")} return _nm_device_state_reason_description[reason] - def frequency_to_channel(frequency): """Returns the channel matching a given radio channel frequency. If a frequency is not in the dictionary channel 1 will be returned. @@ -258,11 +264,12 @@ def frequency_to_channel(frequency): 2452: 9, 2457: 10, 2462: 11, 2467: 12, 2472: 13} if frequency not in ftoc: - logging.warning("The frequency %s can not be mapped to a channel, " \ - "defaulting to channel 1.", frequency) + logging.warning('The frequency %s can not be mapped to a channel, ' + 'defaulting to channel 1.', frequency) return 1 return ftoc[frequency] + def is_sugar_adhoc_network(ssid): """Checks whether an access point is a sugar Ad-hoc network. @@ -294,8 +301,9 @@ class WirelessSecurity(object): wireless_security['group'] = self.group return wireless_security + class Wireless(object): - nm_name = "802-11-wireless" + nm_name = '802-11-wireless' def __init__(self): self.ssid = None @@ -318,7 +326,7 @@ class Wireless(object): class OlpcMesh(object): - nm_name = "802-11-olpc-mesh" + nm_name = '802-11-olpc-mesh' def __init__(self, channel, anycast_addr): self.channel = channel @@ -326,12 +334,12 @@ class OlpcMesh(object): def get_dict(self): ret = { - "ssid": dbus.ByteArray("olpc-mesh"), - "channel": self.channel, + 'ssid': dbus.ByteArray('olpc-mesh'), + 'channel': self.channel, } if self.anycast_addr: - ret["dhcp-anycast-address"] = dbus.ByteArray(self.anycast_addr) + ret['dhcp-anycast-address'] = dbus.ByteArray(self.anycast_addr) return ret @@ -352,6 +360,7 @@ class Connection(object): connection['timestamp'] = self.timestamp return connection + class IP4Config(object): def __init__(self): self.method = None @@ -362,6 +371,7 @@ class IP4Config(object): ip4_config['method'] = self.method return ip4_config + class Serial(object): def __init__(self): self.baud = None @@ -374,6 +384,7 @@ class Serial(object): return serial + class Ppp(object): def __init__(self): pass @@ -382,6 +393,7 @@ class Ppp(object): ppp = {} return ppp + class Gsm(object): def __init__(self): self.apn = None @@ -400,6 +412,7 @@ class Gsm(object): return gsm + class Settings(object): def __init__(self, wireless_cfg=None): self.connection = Connection() @@ -422,6 +435,7 @@ class Settings(object): settings['ipv4'] = self.ip4_config.get_dict() return settings + class Secrets(object): def __init__(self, settings): self.settings = settings @@ -447,6 +461,7 @@ class Secrets(object): return settings + class SettingsGsm(object): def __init__(self): self.connection = Connection() @@ -466,22 +481,24 @@ class SettingsGsm(object): return settings + class SecretsGsm(object): def __init__(self): self.password = None self.pin = None self.puk = None - + def get_dict(self): secrets = {} if self.password is not None: secrets['password'] = self.password if self.pin is not None: secrets['pin'] = self.pin - if self.puk is not None: + if self.puk is not None: secrets['puk'] = self.puk return {'gsm': secrets} + class NMSettings(dbus.service.Object): def __init__(self): bus = dbus.SystemBus() @@ -509,10 +526,19 @@ class NMSettings(dbus.service.Object): self.secrets_request.send(self, connection=sender, response=kwargs['response']) + def clear_wifi_connections(self): + for uuid in self.connections.keys(): + conn = self.connections[uuid] + if conn._settings.connection.type == \ + NM_CONNECTION_TYPE_802_11_WIRELESS: + conn.Removed() + self.connections.pop(uuid) + + class SecretsResponse(object): - ''' Intermediate object to report the secrets from the dialog + """Intermediate object to report the secrets from the dialog back to the connection object and which will inform NM - ''' + """ def __init__(self, connection, reply_cb, error_cb): self._connection = connection self._reply_cb = reply_cb @@ -525,6 +551,7 @@ class SecretsResponse(object): def set_error(self, error): self._error_cb(error) + class NMSettingsConnection(dbus.service.Object): def __init__(self, path, settings, secrets): bus = dbus.SystemBus() @@ -537,15 +564,26 @@ class NMSettingsConnection(dbus.service.Object): self._settings = settings self._secrets = secrets + @dbus.service.signal(dbus_interface=NM_CONNECTION_IFACE, + signature='') + def Removed(self): + pass + + @dbus.service.signal(dbus_interface=NM_CONNECTION_IFACE, + signature='a{sa{sv}}') + def Updated(self, settings): + pass + def set_connected(self): if self._settings.connection.type == NM_CONNECTION_TYPE_GSM: self._settings.connection.timestamp = int(time.time()) - else: - if not self._settings.connection.autoconnect: - self._settings.connection.autoconnect = True - self._settings.connection.timestamp = int(time.time()) - if self._settings.connection.type == NM_CONNECTION_TYPE_802_11_WIRELESS: - self.save() + elif not self._settings.connection.autoconnect: + self._settings.connection.autoconnect = True + self._settings.connection.timestamp = int(time.time()) + if (self._settings.connection.type == + NM_CONNECTION_TYPE_802_11_WIRELESS): + self.Updated(self._settings.get_dict()) + self.save() try: # try to flush resolver cache - SL#1940 @@ -555,19 +593,28 @@ class NMSettingsConnection(dbus.service.Object): res_init = getattr(libc, '__res_init') res_init(None) except: + # pylint: disable=W0702 logging.exception('Error calling libc.__res_init') + def disable_autoconnect(self): + if self._settings.connection.type != NM_CONNECTION_TYPE_GSM and \ + self._settings.connection.autoconnect: + self._settings.connection.autoconnect = False + self._settings.connection.timestamp = None + self.Updated(self._settings.get_dict()) + self.save() + def set_secrets(self, secrets): self._secrets = secrets - if self._settings.connection.type == NM_CONNECTION_TYPE_802_11_WIRELESS: + if self._settings.connection.type == \ + NM_CONNECTION_TYPE_802_11_WIRELESS: self.save() def get_settings(self): return self._settings def save(self): - profile_path = env.get_profile_path() - config_path = os.path.join(profile_path, 'nm', 'connections.cfg') + config_path = _get_wifi_connections_path() config = ConfigParser.ConfigParser() try: @@ -635,24 +682,28 @@ class NMSettingsConnection(dbus.service.Object): self.path, request_new) if self._settings.connection.type is not NM_CONNECTION_TYPE_GSM: if request_new or self._secrets is None: - # request_new is for example the case when the pw on the AP changes + # request_new is for example the case when the pw on the AP + # changes response = SecretsResponse(self, reply, error) try: self.secrets_request.send(self, response=response) except Exception: - logging.exception('Error requesting the secrets via dialog') + logging.exception('Error requesting the secrets via' + ' dialog') else: reply(self._secrets.get_dict()) else: if not request_new: reply(self._secrets.get_dict()) else: - raise Exception('The stored GSM secret has already been supplied ') + raise Exception('The stored GSM secret has already been' + ' supplied') + class AccessPoint(gobject.GObject): __gsignals__ = { 'props-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) + ([gobject.TYPE_PYOBJECT])), } def __init__(self, device, model): @@ -672,8 +723,7 @@ class AccessPoint(gobject.GObject): self.channel = 0 def initialize(self): - model_props = dbus.Interface(self.model, - 'org.freedesktop.DBus.Properties') + model_props = dbus.Interface(self.model, dbus.PROPERTIES_IFACE) model_props.GetAll(NM_ACCESSPOINT_IFACE, byte_arrays=True, reply_handler=self._ap_properties_changed_cb, error_handler=self._get_all_props_error_cb) @@ -718,7 +768,7 @@ class AccessPoint(gobject.GObject): else: fl |= 1 << 6 - hashstr = str(fl) + "@" + self.name + hashstr = str(fl) + '@' + self.name return hash(hashstr) def _update_properties(self, properties): @@ -757,6 +807,7 @@ class AccessPoint(gobject.GObject): path=self.model.object_path, dbus_interface=NM_ACCESSPOINT_IFACE) + def get_settings(): global _nm_settings if _nm_settings is None: @@ -767,17 +818,20 @@ def get_settings(): load_connections() return _nm_settings + def find_connection_by_ssid(ssid): connections = get_settings().connections for conn_index in connections: connection = connections[conn_index] - if connection._settings.connection.type == NM_CONNECTION_TYPE_802_11_WIRELESS: - if connection._settings.wireless.ssid == ssid: - return connection + if connection._settings.connection.type == \ + NM_CONNECTION_TYPE_802_11_WIRELESS and \ + connection._settings.wireless.ssid == ssid: + return connection return None + def add_connection(uuid, settings, secrets=None): global _conn_counter @@ -788,19 +842,26 @@ def add_connection(uuid, settings, secrets=None): _nm_settings.add_connection(uuid, conn) return conn -def load_wifi_connections(): + +def _get_wifi_connections_path(): profile_path = env.get_profile_path() - config_path = os.path.join(profile_path, 'nm', 'connections.cfg') + return os.path.join(profile_path, 'nm', 'connections.cfg') - config = ConfigParser.ConfigParser() + +def _create_wifi_connections(config_path): + if not os.path.exists(os.path.dirname(config_path)): + os.makedirs(os.path.dirname(config_path), 0755) + f = open(config_path, 'w') + f.close() + + +def load_wifi_connections(): + config_path = _get_wifi_connections_path() if not os.path.exists(config_path): - if not os.path.exists(os.path.dirname(config_path)): - os.makedirs(os.path.dirname(config_path), 0755) - f = open(config_path, 'w') - config.write(f) - f.close() + _create_wifi_connections(config_path) + config = ConfigParser.ConfigParser() try: if not config.read(config_path): logging.error('Error reading the nm config file') @@ -870,10 +931,10 @@ def load_gsm_connection(): if username and number and apn: settings = SettingsGsm() - settings.gsm.username = username + settings.gsm.username = username settings.gsm.number = number settings.gsm.apn = apn - + secrets = SecretsGsm() secrets.pin = pin secrets.puk = puk @@ -891,12 +952,14 @@ def load_gsm_connection(): except Exception: logging.exception('Error adding gsm connection to NMSettings.') else: - logging.exception("No gsm connection was set in GConf.") + logging.warning('No gsm connection was set in GConf.') + def load_connections(): load_wifi_connections() load_gsm_connection() + def find_gsm_connection(): connections = get_settings().connections @@ -906,3 +969,39 @@ def find_gsm_connection(): logging.debug('There is no gsm connection in the NMSettings.') return None + + +def have_wifi_connections(): + return bool(get_settings().connections) + + +def clear_wifi_connections(): + if _nm_settings is not None: + _nm_settings.clear_wifi_connections() + + config_path = _get_wifi_connections_path() + _create_wifi_connections(config_path) + + +def disconnect_access_points(ap_paths): + """ + Disconnect all devices connected to any of the given access points. + """ + bus = dbus.SystemBus() + netmgr_obj = bus.get_object(NM_SERVICE, NM_PATH) + netmgr = dbus.Interface(netmgr_obj, NM_IFACE) + netmgr_props = dbus.Interface(netmgr, dbus.PROPERTIES_IFACE) + active_connection_paths = netmgr_props.Get(NM_IFACE, 'ActiveConnections') + + for conn_path in active_connection_paths: + conn_obj = bus.get_object(NM_IFACE, conn_path) + conn_props = dbus.Interface(conn_obj, dbus.PROPERTIES_IFACE) + ap_path = conn_props.Get(NM_ACTIVE_CONN_IFACE, 'SpecificObject') + if ap_path == '/' or ap_path not in ap_paths: + continue + + dev_paths = conn_props.Get(NM_ACTIVE_CONN_IFACE, 'Devices') + for dev_path in dev_paths: + dev_obj = bus.get_object(NM_SERVICE, dev_path) + dev = dbus.Interface(dev_obj, NM_DEVICE_IFACE) + dev.Disconnect() diff --git a/src/jarabe/model/notifications.py b/src/jarabe/model/notifications.py index f2e2d65..ec14056 100644 --- a/src/jarabe/model/notifications.py +++ b/src/jarabe/model/notifications.py @@ -23,9 +23,13 @@ from sugar import dispatch from jarabe import config -_DBUS_SERVICE = "org.freedesktop.Notifications" -_DBUS_IFACE = "org.freedesktop.Notifications" -_DBUS_PATH = "/org/freedesktop/Notifications" + +_DBUS_SERVICE = 'org.freedesktop.Notifications' +_DBUS_IFACE = 'org.freedesktop.Notifications' +_DBUS_PATH = '/org/freedesktop/Notifications' + +_instance = None + class NotificationService(dbus.service.Object): def __init__(self): @@ -43,7 +47,8 @@ class NotificationService(dbus.service.Object): hints, expire_timeout): logging.debug('Received notification: %r', [app_name, replaces_id, - '<app_icon>', summary, body, actions, '<hints>', expire_timeout]) + '<app_icon>', summary, body, actions, '<hints>', + expire_timeout]) if replaces_id > 0: notification_id = replaces_id @@ -73,16 +78,14 @@ class NotificationService(dbus.service.Object): def GetServerInformation(self, name, vendor, version): return 'Sugar Shell', 'Sugar', config.version - - @dbus.service.signal(_DBUS_IFACE, signature="uu") + @dbus.service.signal(_DBUS_IFACE, signature='uu') def NotificationClosed(self, notification_id, reason): pass - @dbus.service.signal(_DBUS_IFACE, signature="us") + @dbus.service.signal(_DBUS_IFACE, signature='us') def ActionInvoked(self, notification_id, action_key): pass -_instance = None def get_service(): global _instance @@ -90,6 +93,6 @@ def get_service(): _instance = NotificationService() return _instance + def init(): get_service() - diff --git a/src/jarabe/model/olpcmesh.py b/src/jarabe/model/olpcmesh.py index 60f6be4..f070100 100644 --- a/src/jarabe/model/olpcmesh.py +++ b/src/jarabe/model/olpcmesh.py @@ -24,13 +24,14 @@ from jarabe.model.network import Settings from jarabe.model.network import OlpcMesh as OlpcMeshSettings from sugar.util import unique_id + _NM_SERVICE = 'org.freedesktop.NetworkManager' _NM_IFACE = 'org.freedesktop.NetworkManager' _NM_PATH = '/org/freedesktop/NetworkManager' _NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' _NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh' -_XS_ANYCAST = "\xc0\x27\xc0\x27\xc0\x00" +_XS_ANYCAST = '\xc0\x27\xc0\x27\xc0\x00' DEVICE_STATE_UNKNOWN = 0 DEVICE_STATE_UNMANAGED = 1 @@ -43,6 +44,7 @@ DEVICE_STATE_IP_CONFIG = 7 DEVICE_STATE_ACTIVATED = 8 DEVICE_STATE_FAILED = 9 + class OlpcMeshManager(object): def __init__(self, mesh_device): self._bus = dbus.SystemBus() @@ -56,14 +58,12 @@ class OlpcMeshManager(object): """ - props = dbus.Interface(self.mesh_device, - 'org.freedesktop.DBus.Properties') + props = dbus.Interface(self.mesh_device, dbus.PROPERTIES_IFACE) props.Get(_NM_DEVICE_IFACE, 'State', reply_handler=self.__get_mesh_state_reply_cb, error_handler=self.__get_state_error_cb) - props = dbus.Interface(self.eth_device, - 'org.freedesktop.DBus.Properties') + props = dbus.Interface(self.eth_device, dbus.PROPERTIES_IFACE) props.Get(_NM_DEVICE_IFACE, 'State', reply_handler=self.__get_eth_state_reply_cb, error_handler=self.__get_state_error_cb) @@ -88,8 +88,7 @@ class OlpcMeshManager(object): self._start_automesh() def _get_companion_device(self): - props = dbus.Interface(self.mesh_device, - 'org.freedesktop.DBus.Properties') + props = dbus.Interface(self.mesh_device, dbus.PROPERTIES_IFACE) eth_device_o = props.Get(_NM_OLPC_MESH_IFACE, 'Companion') return self._bus.get_object(_NM_SERVICE, eth_device_o) @@ -119,7 +118,7 @@ class OlpcMeshManager(object): def __eth_device_state_changed_cb(self, new_state, old_state, reason): """If a connection is activated on the eth device, stop trying our automatic connections. - + """ self._eth_device_state = new_state self._maybe_schedule_idle_check() @@ -147,7 +146,7 @@ class OlpcMeshManager(object): def _idle_check(self): if self._mesh_device_state == DEVICE_STATE_DISCONNECTED \ and self._eth_device_state == DEVICE_STATE_DISCONNECTED: - logging.debug("starting automesh due to inactivity") + logging.debug('starting automesh due to inactivity') self._start_automesh() return False @@ -170,7 +169,7 @@ class OlpcMeshManager(object): logging.error('Failed to activate connection: %s', err) def _activate_connection(self, channel, anycast_address=None): - logging.debug("activate channel %d anycast %r", + logging.debug('activate channel %d anycast %r', channel, anycast_address) proxy = self._bus.get_object(_NM_SERVICE, _NM_PATH) network_manager = dbus.Interface(proxy, _NM_IFACE) @@ -211,4 +210,3 @@ class OlpcMeshManager(object): self._connection_queue.append((6, _XS_ANYCAST)) self._connection_queue.append((1, _XS_ANYCAST)) self._try_next_connection_from_queue() - diff --git a/src/jarabe/model/screen.py b/src/jarabe/model/screen.py index 4403c1c..7d34d45 100644 --- a/src/jarabe/model/screen.py +++ b/src/jarabe/model/screen.py @@ -18,12 +18,14 @@ import logging import dbus + _HARDWARE_MANAGER_INTERFACE = 'org.freedesktop.ohm.Keystore' _HARDWARE_MANAGER_SERVICE = 'org.freedesktop.ohm' _HARDWARE_MANAGER_OBJECT_PATH = '/org/freedesktop/ohm/Keystore' _ohm_service = None + def _get_ohm(): global _ohm_service if _ohm_service is None: @@ -35,9 +37,9 @@ def _get_ohm(): return _ohm_service + def set_dcon_freeze(frozen): try: - _get_ohm().SetKey("display.dcon_freeze", frozen) + _get_ohm().SetKey('display.dcon_freeze', frozen) except dbus.DBusException: logging.error('Cannot unfreeze the DCON') - diff --git a/src/jarabe/model/session.py b/src/jarabe/model/session.py index 9e0f087..9b277ff 100644 --- a/src/jarabe/model/session.py +++ b/src/jarabe/model/session.py @@ -24,8 +24,10 @@ import logging from sugar import session from sugar import env + _session_manager = None + class SessionManager(session.SessionManager): MODE_LOGOUT = 0 MODE_SHUTDOWN = 1 @@ -53,15 +55,15 @@ class SessionManager(session.SessionManager): elif self._logout_mode != self.MODE_LOGOUT: try: bus = dbus.SystemBus() - proxy = bus.get_object('org.freedesktop.Hal', - '/org/freedesktop/Hal/devices/computer') + proxy = bus.get_object('org.freedesktop.ConsoleKit', + '/org/freedesktop/ConsoleKit/Manager') pm = dbus.Interface(proxy, - 'org.freedesktop.Hal.Device.SystemPowerManagement') + 'org.freedesktop.ConsoleKit.Manager') if self._logout_mode == self.MODE_SHUTDOWN: - pm.Shutdown() + pm.Stop() elif self._logout_mode == self.MODE_REBOOT: - pm.Reboot() + pm.Restart() except: logging.exception('Can not stop sugar') self.session.cancel_shutdown() @@ -73,14 +75,15 @@ class SessionManager(session.SessionManager): def _close_emulator(self): gtk.main_quit() - if os.environ.has_key('SUGAR_EMULATOR_PID'): + if 'SUGAR_EMULATOR_PID' in os.environ: pid = int(os.environ['SUGAR_EMULATOR_PID']) os.kill(pid, signal.SIGTERM) - # Need to call this ASAP so the atexit handlers get called before we get - # killed by the X (dis)connection + # Need to call this ASAP so the atexit handlers get called before we + # get killed by the X (dis)connection sys.exit() + def get_session_manager(): global _session_manager diff --git a/src/jarabe/model/shell.py b/src/jarabe/model/shell.py index db0e050..63f6173 100644 --- a/src/jarabe/model/shell.py +++ b/src/jarabe/model/shell.py @@ -31,9 +31,11 @@ from sugar.graphics.xocolor import XoColor from jarabe.model.bundleregistry import get_registry from jarabe.model import neighborhood -_SERVICE_NAME = "org.laptop.Activity" -_SERVICE_PATH = "/org/laptop/Activity" -_SERVICE_INTERFACE = "org.laptop.Activity" +_SERVICE_NAME = 'org.laptop.Activity' +_SERVICE_PATH = '/org/laptop/Activity' +_SERVICE_INTERFACE = 'org.laptop.Activity' + +_model = None class Activity(gobject.GObject): @@ -60,11 +62,12 @@ class Activity(gobject.GObject): the "type" of activity being created. activity_id -- unique identifier for this instance of the activity type - window -- Main WnckWindow of the activity + _windows -- WnckWindows registered for the activity. The lowest + one in the stack is the main window. """ gobject.GObject.__init__(self) - self._window = None + self._windows = [] self._service = None self._activity_id = activity_id self._activity_info = activity_info @@ -72,7 +75,7 @@ class Activity(gobject.GObject): self._launch_status = Activity.LAUNCHING if window is not None: - self.set_window(window) + self.add_window(window) self._retrieve_service() @@ -81,8 +84,8 @@ class Activity(gobject.GObject): bus = dbus.SessionBus() self._name_owner_changed_handler = bus.add_signal_receiver( self._name_owner_changed_cb, - signal_name="NameOwnerChanged", - dbus_interface="org.freedesktop.DBus") + signal_name='NameOwnerChanged', + dbus_interface='org.freedesktop.DBus') self._launch_completed_hid = get_model().connect('launch-completed', self.__launch_completed_cb) @@ -94,15 +97,19 @@ class Activity(gobject.GObject): launch_status = gobject.property(getter=get_launch_status) - def set_window(self, window): - """Set the window for the activity - - We allow resetting the window for an activity so that we - can replace the launcher once we get its real window. - """ + def add_window(self, window): + """Add a window to the windows stack.""" if not window: - raise ValueError("window must be valid") - self._window = window + raise ValueError('window must be valid') + self._windows.append(window) + + def remove_window_by_xid(self, xid): + """Remove a window from the windows stack.""" + for wnd in self._windows: + if wnd.get_xid() == xid: + self._windows.remove(wnd) + return True + return False def get_service(self): """Get the activity service @@ -116,8 +123,8 @@ class Activity(gobject.GObject): def get_title(self): """Retrieve the application's root window's suggested title""" - if self._window: - return self._window.get_name() + if self._windows: + return self._windows[0].get_name() else: return '' @@ -157,7 +164,7 @@ class Activity(gobject.GObject): return activity.props.color else: client = gconf.client_get_default() - return XoColor(client.get_string("/desktop/sugar/user/color")) + return XoColor(client.get_string('/desktop/sugar/user/color')) def get_activity_id(self): """Retrieve the "activity_id" passed in to our constructor @@ -169,31 +176,44 @@ class Activity(gobject.GObject): def get_xid(self): """Retrieve the X-windows ID of our root window""" - if self._window is not None: - return self._window.get_xid() + if self._windows: + return self._windows[0].get_xid() else: return None + def has_xid(self, xid): + """Check if an X-window with the given xid is in the windows stack""" + if self._windows: + for wnd in self._windows: + if wnd.get_xid() == xid: + return True + return False + def get_window(self): """Retrieve the X-windows root window of this application - This was stored by the set_window method, which was + This was stored by the add_window method, which was called by HomeModel._add_activity, which was called via a callback that looks for all 'window-opened' events. + We keep a stack of the windows. The lowest window in the + stack that is still valid we consider the main one. + HomeModel currently uses a dbus service query on the activity to determine to which HomeActivity the newly launched window belongs. """ - return self._window + if self._windows: + return self._windows[0] + return None def get_type(self): """Retrieve the activity bundle id for future reference""" - if self._window is None: + if not self._windows: return None else: - return wm.get_bundle_id(self._window) + return wm.get_bundle_id(self._windows[0]) def is_journal(self): """Returns boolean if the activity is of type JournalActivity""" @@ -209,7 +229,9 @@ class Activity(gobject.GObject): def get_pid(self): """Returns the activity's PID""" - return self._window.get_pid() + if not self._windows: + return None + return self._windows[0].get_pid() def get_bundle_path(self): """Returns the activity's bundle directory""" @@ -228,8 +250,8 @@ class Activity(gobject.GObject): def equals(self, activity): if self._activity_id and activity.get_activity_id(): return self._activity_id == activity.get_activity_id() - if self._window.get_xid() and activity.get_xid(): - return self._window.get_xid() == activity.get_xid() + if self._windows[0].get_xid() and activity.get_xid(): + return self._windows[0].get_xid() == activity.get_xid() return False def _get_service_name(self): @@ -245,7 +267,7 @@ class Activity(gobject.GObject): try: bus = dbus.SessionBus() proxy = bus.get_object(self._get_service_name(), - _SERVICE_PATH + "/" + self._activity_id) + _SERVICE_PATH + '/' + self._activity_id) self._service = dbus.Interface(proxy, _SERVICE_INTERFACE) except dbus.DBusException: self._service = None @@ -275,7 +297,7 @@ class Activity(gobject.GObject): pass def _set_active_error(self, err): - logging.error("set_active() failed: %s", err) + logging.error('set_active() failed: %s', err) def _set_launch_status(self, value): get_model().disconnect(self._launch_completed_hid) @@ -310,27 +332,22 @@ class ShellModel(gobject.GObject): """ __gsignals__ = { - 'activity-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'activity-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), + 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'activity-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), 'active-activity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), + ([gobject.TYPE_PYOBJECT])), 'tabbing-activity-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'launch-started': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'launch-completed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'launch-failed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) + gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'launch-started': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'launch-completed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'launch-failed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), } ZOOM_MESH = 0 @@ -460,7 +477,7 @@ class ShellModel(gobject.GObject): def set_tabbing_activity(self, activity): """Sets the activity that is currently highlighted during tabbing""" self._tabbing_activity = activity - self.emit("tabbing-activity-changed", self._tabbing_activity) + self.emit('tabbing-activity-changed', self._tabbing_activity) def _set_active_activity(self, home_activity): if self._active_activity == home_activity: @@ -488,6 +505,21 @@ class ShellModel(gobject.GObject): return self._activities.index(obj) def _window_opened_cb(self, screen, window): + """Handle the callback for the 'window opened' event. + + Most activities will register 2 windows during + their lifetime: the launcher window, and the 'main' + app window. + + When the main window appears, we send a signal to + the launcher window to close. + + Some activities (notably non-native apps) open several + windows during their lifetime, switching from one to + the next as the 'main' window. We use a stack to track + them. + + """ if window.get_window_type() == wnck.WINDOW_NORMAL: home_activity = None @@ -510,29 +542,36 @@ class ShellModel(gobject.GObject): window.maximize() if not home_activity: + logging.debug('first window registered for %s' % activity_id) home_activity = Activity(activity_info, activity_id, window) self._add_activity(home_activity) else: - home_activity.set_window(window) + logging.debug('window registered for %s' % activity_id) + home_activity.add_window(window) - if wm.get_sugar_window_type(window) != 'launcher': + if wm.get_sugar_window_type(window) != 'launcher' \ + and home_activity.get_launch_status() == Activity.LAUNCHING: self.emit('launch-completed', home_activity) - startup_time = time.time() - home_activity.get_launch_time() - logging.debug('%s launched in %f seconds.', - home_activity.get_type(), startup_time) + logging.debug('%s launched in %f seconds.' % + (activity_id, startup_time)) if self._active_activity is None: self._set_active_activity(home_activity) def _window_closed_cb(self, screen, window): if window.get_window_type() == wnck.WINDOW_NORMAL: - if self._get_activity_by_xid(window.get_xid()) is not None: - self._remove_activity_by_xid(window.get_xid()) + xid = window.get_xid() + activity = self._get_activity_by_xid(xid) + if activity is not None: + activity.remove_window_by_xid(xid) + if activity.get_window() is None: + logging.debug('last window gone - remove activity %s' % activity) + self._remove_activity(activity) def _get_activity_by_xid(self, xid): for home_activity in self._activities: - if home_activity.get_xid() == xid: + if home_activity.has_xid(xid): return home_activity return None @@ -577,13 +616,6 @@ class ShellModel(gobject.GObject): self.emit('activity-removed', home_activity) self._activities.remove(home_activity) - def _remove_activity_by_xid(self, xid): - home_activity = self._get_activity_by_xid(xid) - if home_activity: - self._remove_activity(home_activity) - else: - logging.error('Model for window %d does not exist.', xid) - def notify_launch(self, activity_id, service_name): registry = get_registry() activity_info = registry.get_bundle(service_name) @@ -606,7 +638,7 @@ class ShellModel(gobject.GObject): def notify_launch_failed(self, activity_id): home_activity = self.get_activity_by_id(activity_id) if home_activity: - logging.debug("Activity %s (%s) launch failed", activity_id, + logging.debug('Activity %s (%s) launch failed', activity_id, home_activity.get_type()) if self.get_launcher(activity_id) is not None: self.emit('launch-failed', home_activity) @@ -631,11 +663,8 @@ class ShellModel(gobject.GObject): return False -_model = None - def get_model(): global _model if _model is None: _model = ShellModel() return _model - diff --git a/src/jarabe/model/sound.py b/src/jarabe/model/sound.py index 65090a4..9e1e748 100644 --- a/src/jarabe/model/sound.py +++ b/src/jarabe/model/sound.py @@ -20,17 +20,23 @@ from sugar import env from sugar import _sugarext from sugar import dispatch + VOLUME_STEP = 10 muted_changed = dispatch.Signal() volume_changed = dispatch.Signal() +_volume = _sugarext.VolumeAlsa() + + def get_muted(): return _volume.get_mute() + def get_volume(): return _volume.get_volume() + def set_volume(new_volume): old_volume = _volume.get_volume() _volume.set_volume(new_volume) @@ -38,6 +44,7 @@ def set_volume(new_volume): volume_changed.send(None) save() + def set_muted(new_state): old_state = _volume.get_mute() _volume.set_mute(new_state) @@ -45,14 +52,14 @@ def set_muted(new_state): muted_changed.send(None) save() + def save(): if env.is_emulator() is False: client = gconf.client_get_default() client.set_int('/desktop/sugar/sound/volume', get_volume()) + def restore(): if env.is_emulator() is False: client = gconf.client_get_default() set_volume(client.get_int('/desktop/sugar/sound/volume')) - -_volume = _sugarext.VolumeAlsa() diff --git a/src/jarabe/model/telepathyclient.py b/src/jarabe/model/telepathyclient.py index f4eccc3..c6fbac1 100644 --- a/src/jarabe/model/telepathyclient.py +++ b/src/jarabe/model/telepathyclient.py @@ -26,9 +26,13 @@ from telepathy.server import DBusProperties from sugar import dispatch + SUGAR_CLIENT_SERVICE = 'org.freedesktop.Telepathy.Client.Sugar' SUGAR_CLIENT_PATH = '/org/freedesktop/Telepathy/Client/Sugar' +_instance = None + + class TelepathyClient(dbus.service.Object, DBusProperties): def __init__(self): self._interfaces = set([CLIENT, CLIENT_HANDLER, @@ -91,7 +95,6 @@ class TelepathyClient(dbus.service.Object, DBusProperties): except Exception, e: logging.exception(e) -_instance = None def get_instance(): global _instance diff --git a/src/jarabe/util/__init__.py b/src/jarabe/util/__init__.py index 1610dd0..9c80ecb 100644 --- a/src/jarabe/util/__init__.py +++ b/src/jarabe/util/__init__.py @@ -16,4 +16,3 @@ # 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 - diff --git a/src/jarabe/util/emulator.py b/src/jarabe/util/emulator.py index 6a43044..fda1b59 100644 --- a/src/jarabe/util/emulator.py +++ b/src/jarabe/util/emulator.py @@ -30,37 +30,37 @@ from sugar import env ERROR_NO_DISPLAY = 30 ERROR_NO_SERVER = 31 +default_dimensions = (800, 600) -default_dimensions = (800, 600) def _run_xephyr(display, dpi, dimensions, fullscreen): - cmd = [ 'Xephyr' ] + cmd = ['Xephyr'] cmd.append(':%d' % display) - cmd.append('-ac') + cmd.append('-ac') cmd += ['-title', _('Sugar in a window')] screen_size = (gtk.gdk.screen_width(), gtk.gdk.screen_height()) if (not dimensions) and (fullscreen is None) and \ - (screen_size < default_dimensions) : + (screen_size <= default_dimensions): # no forced settings, screen too small => fit screen fullscreen = True - elif (not dimensions) : + elif not dimensions: # screen is big enough or user has en/disabled fullscreen manually # => use default size (will get ignored for fullscreen) dimensions = '%dx%d' % default_dimensions - if not dpi : + if not dpi: dpi = gtk.settings_get_default().get_property('gtk-xft-dpi') / 1024 - if fullscreen : + if fullscreen: cmd.append('-fullscreen') - if dimensions : + if dimensions: cmd.append('-screen') cmd.append(dimensions) - if dpi : + if dpi: cmd.append('-dpi') cmd.append('%d' % dpi) @@ -78,8 +78,8 @@ def _run_xephyr(display, dpi, dimensions, fullscreen): def _check_server(display): result = subprocess.call(['xdpyinfo', '-display', ':%d' % display], - stdout=open(os.devnull, "w"), - stderr=open(os.devnull, "w")) + stdout=open(os.devnull, 'w'), + stderr=open(os.devnull, 'w')) return result == 0 @@ -118,6 +118,7 @@ def _start_window_manager(): gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH) + def _setup_env(display, scaling, emulator_pid): os.environ['SUGAR_EMULATOR'] = 'yes' os.environ['GABBLE_LOGFILE'] = os.path.join( @@ -128,7 +129,7 @@ def _setup_env(display, scaling, emulator_pid): env.get_profile_path(), 'logs', 'mission-control.log') os.environ['STREAM_ENGINE_LOGFILE'] = os.path.join( env.get_profile_path(), 'logs', 'telepathy-stream-engine.log') - os.environ['DISPLAY'] = ":%d" % (display) + os.environ['DISPLAY'] = ':%d' % (display) os.environ['SUGAR_EMULATOR_PID'] = emulator_pid os.environ['MC_ACCOUNT_DIR'] = os.path.join( env.get_profile_path(), 'accounts') @@ -136,11 +137,12 @@ def _setup_env(display, scaling, emulator_pid): if scaling: os.environ['SUGAR_SCALING'] = scaling + def main(): """Script-level operations""" parser = OptionParser() - parser.add_option('-d', '--dpi', dest='dpi', type="int", + parser.add_option('-d', '--dpi', dest='dpi', type='int', help='Emulator dpi') parser.add_option('-s', '--scaling', dest='scaling', help='Sugar scaling in %') diff --git a/src/jarabe/util/telepathy/__init__.py b/src/jarabe/util/telepathy/__init__.py index 387d09c..eee4abb 100644 --- a/src/jarabe/util/telepathy/__init__.py +++ b/src/jarabe/util/telepathy/__init__.py @@ -16,4 +16,3 @@ # 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 - diff --git a/src/jarabe/util/telepathy/connection_watcher.py b/src/jarabe/util/telepathy/connection_watcher.py index 391bdd5..96af1cf 100644 --- a/src/jarabe/util/telepathy/connection_watcher.py +++ b/src/jarabe/util/telepathy/connection_watcher.py @@ -28,12 +28,16 @@ from telepathy.interfaces import CONN_INTERFACE from telepathy.constants import CONNECTION_STATUS_CONNECTED, \ CONNECTION_STATUS_DISCONNECTED + +_instance = None + + class ConnectionWatcher(gobject.GObject): __gsignals__ = { 'connection-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), 'connection-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) + ([gobject.TYPE_PYOBJECT])), } def __init__(self, bus=None): @@ -93,7 +97,6 @@ class ConnectionWatcher(gobject.GObject): def get_connections(self): return self._connections.values() -_instance = None def get_instance(): global _instance @@ -101,14 +104,15 @@ def get_instance(): _instance = ConnectionWatcher() return _instance + if __name__ == '__main__': dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) def connection_added_cb(conn_watcher, conn): - print "new connection", conn.service_name + print 'new connection', conn.service_name def connection_removed_cb(conn_watcher, conn): - print "removed connection", conn.service_name + print 'removed connection', conn.service_name watcher = ConnectionWatcher() watcher.connect('connection-added', connection_added_cb) diff --git a/src/jarabe/view/__init__.py b/src/jarabe/view/__init__.py index a9dd95a..85f6a24 100644 --- a/src/jarabe/view/__init__.py +++ b/src/jarabe/view/__init__.py @@ -13,4 +13,3 @@ # 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 - diff --git a/src/jarabe/view/buddyicon.py b/src/jarabe/view/buddyicon.py index 37b9167..a274605 100644 --- a/src/jarabe/view/buddyicon.py +++ b/src/jarabe/view/buddyicon.py @@ -19,6 +19,7 @@ from sugar.graphics import style from jarabe.view.buddymenu import BuddyMenu + class BuddyIcon(CanvasIcon): def __init__(self, buddy, size=style.STANDARD_ICON_SIZE): CanvasIcon.__init__(self, icon_name='computer-xo', size=size) @@ -28,6 +29,8 @@ class BuddyIcon(CanvasIcon): self._buddy.connect('notify::present', self.__buddy_notify_present_cb) self._buddy.connect('notify::color', self.__buddy_notify_color_cb) + self.palette_invoker.cache_palette = False + self._update_color() def create_palette(self): @@ -58,4 +61,3 @@ class BuddyIcon(CanvasIcon): self._greyed_out = (self._buddy.get_nick().lower().find(query) == -1) \ and not self._buddy.is_owner() self._update_color() - diff --git a/src/jarabe/view/buddymenu.py b/src/jarabe/view/buddymenu.py index 0ba6cc1..f824e70 100644 --- a/src/jarabe/view/buddymenu.py +++ b/src/jarabe/view/buddymenu.py @@ -30,6 +30,8 @@ from jarabe.model import shell from jarabe.model import friends from jarabe.model.session import get_session_manager from jarabe.controlpanel.gui import ControlPanel +import jarabe.desktop.homewindow + class BuddyMenu(Palette): def __init__(self, buddy): @@ -86,6 +88,12 @@ class BuddyMenu(Palette): client = gconf.client_get_default() + if client.get_bool('/desktop/sugar/show_restart'): + item = MenuItem(_('Restart'), 'system-restart') + item.connect('activate', self.__reboot_activate_cb) + self.menu.append(item) + item.show() + if client.get_bool('/desktop/sugar/show_logout'): item = MenuItem(_('Logout'), 'system-logout') item.connect('activate', self.__logout_activate_cb) @@ -97,17 +105,18 @@ class BuddyMenu(Palette): self.menu.append(item) item.show() + def _quit(self, action): + home_window = jarabe.desktop.homewindow.get_instance() + home_window.busy_during_delayed_action(action) + def __logout_activate_cb(self, menu_item): - session_manager = get_session_manager() - session_manager.logout() + self._quit(get_session_manager().logout) def __reboot_activate_cb(self, menu_item): - session_manager = get_session_manager() - session_manager.reboot() + self._quit(get_session_manager().reboot) def __shutdown_activate_cb(self, menu_item): - session_manager = get_session_manager() - session_manager.shutdown() + self._quit(get_session_manager().shutdown) def __controlpanel_activate_cb(self, menu_item): panel = ControlPanel() diff --git a/src/jarabe/view/keyhandler.py b/src/jarabe/view/keyhandler.py index 8a85ac7..d79bfe6 100644 --- a/src/jarabe/view/keyhandler.py +++ b/src/jarabe/view/keyhandler.py @@ -17,7 +17,6 @@ import os import logging -import traceback import dbus import gtk @@ -32,41 +31,46 @@ from jarabe.model.shell import ShellModel from jarabe import config from jarabe.journal import journalactivity + _VOLUME_STEP = sound.VOLUME_STEP _VOLUME_MAX = 100 _TABBING_MODIFIER = gtk.gdk.MOD1_MASK + _actions_table = { - 'F1' : 'zoom_mesh', - 'F2' : 'zoom_group', - 'F3' : 'zoom_home', - 'F4' : 'zoom_activity', - 'F5' : 'open_search', - 'F6' : 'frame', - 'XF86AudioMute' : 'volume_mute', - 'F11' : 'volume_down', - 'XF86AudioLowerVolume' : 'volume_down', - 'F12' : 'volume_up', - 'XF86AudioRaiseVolume' : 'volume_up', - '<alt>F11' : 'volume_min', - '<alt>F12' : 'volume_max', - '0x93' : 'frame', - '<alt>Tab' : 'next_window', - '<alt><shift>Tab' : 'previous_window', - '<alt>Escape' : 'close_window', - '0xDC' : 'open_search', + 'F1': 'zoom_mesh', + 'F2': 'zoom_group', + 'F3': 'zoom_home', + 'F4': 'zoom_activity', + 'F5': 'open_search', + 'F6': 'frame', + 'XF86AudioMute': 'volume_mute', + 'F11': 'volume_down', + 'XF86AudioLowerVolume': 'volume_down', + 'F12': 'volume_up', + 'XF86AudioRaiseVolume': 'volume_up', + '<alt>F11': 'volume_min', + '<alt>F12': 'volume_max', + '0x93': 'frame', + '<alt>Tab': 'next_window', + '<alt><shift>Tab': 'previous_window', + '<alt>Escape': 'close_window', + '0xDC': 'open_search', # the following are intended for emulator users - '<alt><shift>f' : 'frame', - '<alt><shift>q' : 'quit_emulator', - 'XF86Search' : 'open_search', - '<alt><shift>o' : 'open_search', - '<alt><shift>s' : 'say_text', + '<alt><shift>f': 'frame', + '<alt><shift>q': 'quit_emulator', + 'XF86Search': 'open_search', + '<alt><shift>o': 'open_search', + '<alt><shift>s': 'say_text', } SPEECH_DBUS_SERVICE = 'org.laptop.Speech' SPEECH_DBUS_PATH = '/org/laptop/Speech' SPEECH_DBUS_INTERFACE = 'org.laptop.Speech' +_instance = None + + class KeyHandler(object): def __init__(self, frame): self._frame = frame @@ -95,8 +99,7 @@ class KeyHandler(object): raise ValueError('Key %r is already bound' % key) _actions_table[key] = module except Exception: - logging.error('Exception while loading extension:\n' + \ - traceback.format_exc()) + logging.exception('Exception while loading extension:') self._key_grabber.grab_keys(_actions_table.keys()) @@ -126,11 +129,11 @@ class KeyHandler(object): def _primary_selection_cb(self, clipboard, text, user_data): logging.debug('KeyHandler._primary_selection_cb: %r', text) if text: - self._get_speech_proxy().SayText(text, reply_handler=lambda: None, \ + self._get_speech_proxy().SayText(text, reply_handler=lambda: None, error_handler=self._on_speech_err) def handle_say_text(self, event_time): - clipboard = gtk.clipboard_get(selection="PRIMARY") + clipboard = gtk.clipboard_get(selection='PRIMARY') clipboard.request_text(self._primary_selection_cb) def handle_previous_window(self, event_time): @@ -197,7 +200,7 @@ class KeyHandler(object): if self._tabbing_handler.is_tabbing(): # Only accept window tabbing events, everything else # cancels the tabbing operation. - if not action in ["next_window", "previous_window"]: + if not action in ['next_window', 'previous_window']: self._tabbing_handler.stop(event_time) return True @@ -220,7 +223,7 @@ class KeyHandler(object): return False def _key_released_cb(self, grabber, keycode, state, event_time): - logging.debug('_key_released_cb: %i %i' % (keycode, state)) + logging.debug('_key_released_cb: %i %i', keycode, state) if self._tabbing_handler.is_tabbing(): # We stop tabbing and switch to the new window as soon as the # modifier key is raised again. @@ -230,7 +233,6 @@ class KeyHandler(object): return True return False -_instance = None def setup(frame): global _instance @@ -239,4 +241,3 @@ def setup(frame): del _instance _instance = KeyHandler(frame) - diff --git a/src/jarabe/view/palettes.py b/src/jarabe/view/palettes.py index 43612d4..d9c1f6b 100644 --- a/src/jarabe/view/palettes.py +++ b/src/jarabe/view/palettes.py @@ -28,13 +28,12 @@ from sugar.graphics.menuitem import MenuItem from sugar.graphics.icon import Icon from sugar.graphics import style from sugar.graphics.xocolor import XoColor -from sugar.activity import activityfactory -from sugar.activity.activityhandle import ActivityHandle from jarabe.model import shell from jarabe.view.viewsource import setup_view_source from jarabe.journal import misc + class BasePalette(Palette): def __init__(self, home_activity): Palette.__init__(self) @@ -109,10 +108,6 @@ class CurrentActivityPalette(BasePalette): self._home_activity.get_window().activate( \ gtk.get_current_event_time()) - def __active_window_changed_cb(self, screen, previous_window=None): - setup_view_source() - self._screen.disconnect(self._active_window_changed_sid) - def __stop_activate_cb(self, menu_item): self._home_activity.get_window().close(1) @@ -124,7 +119,7 @@ class ActivityPalette(Palette): self._activity_info = activity_info client = gconf.client_get_default() - color = XoColor(client.get_string("/desktop/sugar/user/color")) + color = XoColor(client.get_string('/desktop/sugar/user/color')) activity_icon = Icon(file=activity_info.get_icon(), xo_color=color, icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR) @@ -147,6 +142,7 @@ class ActivityPalette(Palette): self.popdown(immediate=True) misc.launch(self._activity_info) + class JournalPalette(BasePalette): def __init__(self, home_activity): self._home_activity = home_activity @@ -198,6 +194,7 @@ class JournalPalette(BasePalette): self._free_space_label.props.label = _('%(free_space)d MB Free') % \ {'free_space': free_space / (1024 * 1024)} + class VolumePalette(Palette): def __init__(self, mount): Palette.__init__(self, label=mount.get_name()) @@ -247,4 +244,3 @@ class VolumePalette(Palette): self._progress_bar.props.fraction = fraction self._free_space_label.props.label = _('%(free_space)d MB Free') % \ {'free_space': free_space / (1024 * 1024)} - diff --git a/src/jarabe/view/pulsingicon.py b/src/jarabe/view/pulsingicon.py index 43ec358..392a404 100644 --- a/src/jarabe/view/pulsingicon.py +++ b/src/jarabe/view/pulsingicon.py @@ -21,9 +21,11 @@ import gobject from sugar.graphics.icon import Icon, CanvasIcon + _INTERVAL = 100 _STEP = math.pi / 10 # must be a fraction of pi, for clean caching + class Pulser(object): def __init__(self, icon): self._pulse_hid = None @@ -83,6 +85,7 @@ class Pulser(object): return True + class PulsingIcon(Icon): __gtype_name__ = 'SugarPulsingIcon' @@ -161,6 +164,7 @@ class PulsingIcon(Icon): if self._palette is not None: self._palette.destroy() + class CanvasPulsingIcon(CanvasIcon): __gtype_name__ = 'SugarCanvasPulsingIcon' diff --git a/src/jarabe/view/service.py b/src/jarabe/view/service.py index 7af778a..29e46b2 100644 --- a/src/jarabe/view/service.py +++ b/src/jarabe/view/service.py @@ -23,9 +23,11 @@ import gtk from jarabe.model import shell from jarabe.model import bundleregistry -_DBUS_SERVICE = "org.laptop.Shell" -_DBUS_SHELL_IFACE = "org.laptop.Shell" -_DBUS_PATH = "/org/laptop/Shell" + +_DBUS_SERVICE = 'org.laptop.Shell' +_DBUS_SHELL_IFACE = 'org.laptop.Shell' +_DBUS_PATH = '/org/laptop/Shell' + class UIService(dbus.service.Object): """Provides d-bus service to script the shell's operations @@ -54,7 +56,7 @@ class UIService(dbus.service.Object): self._shell_model = shell.get_model() @dbus.service.method(_DBUS_SHELL_IFACE, - in_signature="s", out_signature="s") + in_signature='s', out_signature='s') def GetBundlePath(self, bundle_id): bundle = bundleregistry.get_registry().get_bundle(bundle_id) if bundle: @@ -63,11 +65,11 @@ class UIService(dbus.service.Object): return '' @dbus.service.method(_DBUS_SHELL_IFACE, - in_signature="s", out_signature="b") + in_signature='s', out_signature='b') def ActivateActivity(self, activity_id): - """Switch to the window related to this activity_id and return a boolean - indicating if there is a real (ie. not a launcher window) activity - already open. + """Switch to the window related to this activity_id and return a + boolean indicating if there is a real (ie. not a launcher window) + activity already open. """ activity = self._shell_model.get_activity_by_id(activity_id) @@ -78,12 +80,11 @@ class UIService(dbus.service.Object): return False @dbus.service.method(_DBUS_SHELL_IFACE, - in_signature="ss", out_signature="") + in_signature='ss', out_signature='') def NotifyLaunch(self, bundle_id, activity_id): shell.get_model().notify_launch(activity_id, bundle_id) @dbus.service.method(_DBUS_SHELL_IFACE, - in_signature="s", out_signature="") + in_signature='s', out_signature='') def NotifyLaunchFailure(self, activity_id): shell.get_model().notify_launch_failed(activity_id) - diff --git a/src/jarabe/view/tabbinghandler.py b/src/jarabe/view/tabbinghandler.py index f52bda3..0889792 100644 --- a/src/jarabe/view/tabbinghandler.py +++ b/src/jarabe/view/tabbinghandler.py @@ -21,8 +21,10 @@ import gtk from jarabe.model import shell + _RAISE_DELAY = 250 + class TabbingHandler(object): def __init__(self, frame, modifier): self._frame = frame @@ -145,4 +147,3 @@ class TabbingHandler(object): def is_tabbing(self): return self._tabbing - diff --git a/src/jarabe/view/viewsource.py b/src/jarabe/view/viewsource.py index 290df18..a1c0be3 100644 --- a/src/jarabe/view/viewsource.py +++ b/src/jarabe/view/viewsource.py @@ -17,7 +17,6 @@ import os import logging -import traceback from gettext import gettext as _ import gobject @@ -37,11 +36,13 @@ from sugar.bundle.activitybundle import ActivityBundle from sugar.datastore import datastore from sugar import mime + _SOURCE_FONT = pango.FontDescription('Monospace %d' % style.FONT_SIZE) _logger = logging.getLogger('ViewSource') map_activity_to_window = {} + def setup_view_source(activity): service = activity.get_service() if service is not None: @@ -52,9 +53,9 @@ def setup_view_source(activity): expected_exceptions = ['org.freedesktop.DBus.Error.UnknownMethod', 'org.freedesktop.DBus.Python.NotImplementedError'] if e.get_dbus_name() not in expected_exceptions: - logging.error(traceback.format_exc()) + logging.exception('Exception occured in HandleViewSource():') except Exception: - logging.error(traceback.format_exc()) + logging.exception('Exception occured in HandleViewSource():') window_xid = activity.get_xid() if window_xid is None: @@ -76,9 +77,9 @@ def setup_view_source(activity): expected_exceptions = ['org.freedesktop.DBus.Error.UnknownMethod', 'org.freedesktop.DBus.Python.NotImplementedError'] if e.get_dbus_name() not in expected_exceptions: - logging.error(traceback.format_exc()) + logging.exception('Exception occured in GetDocumentPath():') except Exception: - logging.error(traceback.format_exc()) + logging.exception('Exception occured in GetDocumentPath():') if bundle_path is None and document_path is None: _logger.debug('Activity without bundle_path nor document_path') @@ -89,6 +90,7 @@ def setup_view_source(activity): map_activity_to_window[window_xid] = view_source view_source.show() + class ViewSource(gtk.Window): __gtype_name__ = 'SugarViewSource' @@ -129,10 +131,10 @@ class ViewSource(gtk.Window): file_name = '' activity_bundle = ActivityBundle(bundle_path) - command = activity_bundle.get_command() + command = activity_bundle.get_command() if len(command.split(' ')) > 1: name = command.split(' ')[1].split('.')[0] - file_name = name + '.py' + file_name = name + '.py' path = os.path.join(activity_bundle.get_path(), file_name) self._selected_file = path @@ -195,6 +197,7 @@ class ViewSource(gtk.Window): else: self._source_display.file_path = None + class DocumentButton(RadioToolButton): __gtype_name__ = 'SugarDocumentButton' @@ -244,22 +247,20 @@ class DocumentButton(RadioToolButton): error_handler=self.__internal_save_error_cb) def __internal_save_cb(self): - logging.debug("Saved Source object to datastore.") + logging.debug('Saved Source object to datastore.') self._jobject.destroy() def __internal_save_error_cb(self, err): logging.debug('Error saving Source object to datastore: %s', err) self._jobject.destroy() + class Toolbar(gtk.Toolbar): __gtype_name__ = 'SugarViewSourceToolbar' __gsignals__ = { - 'stop-clicked': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([])), - 'source-selected': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, + 'stop-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'source-selected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])), } @@ -339,6 +340,7 @@ class Toolbar(gtk.Toolbar): if button.props.active: self.emit('source-selected', path) + class FileViewer(gtk.ScrolledWindow): __gtype_name__ = 'SugarFileViewer' @@ -405,6 +407,7 @@ class FileViewer(gtk.ScrolledWindow): file_path = model.get_value(tree_iter, 1) self.emit('file-selected', file_path) + class SourceDisplay(gtk.ScrolledWindow): __gtype_name__ = 'SugarSourceDisplay' @@ -461,4 +464,3 @@ class SourceDisplay(gtk.ScrolledWindow): return self._file_path file_path = property(_get_file_path, _set_file_path) - |