diff options
Diffstat (limited to 'src/jarabe/journal')
-rw-r--r-- | src/jarabe/journal/Makefile.am | 1 | ||||
-rw-r--r-- | src/jarabe/journal/detailview.py | 4 | ||||
-rw-r--r-- | src/jarabe/journal/expandedentry.py | 23 | ||||
-rw-r--r-- | src/jarabe/journal/journalactivity.py | 34 | ||||
-rw-r--r-- | src/jarabe/journal/journalentrybundle.py | 4 | ||||
-rw-r--r-- | src/jarabe/journal/journaltoolbox.py | 30 | ||||
-rw-r--r-- | src/jarabe/journal/journalwindow.py | 33 | ||||
-rw-r--r-- | src/jarabe/journal/keepicon.py | 1 | ||||
-rw-r--r-- | src/jarabe/journal/listmodel.py | 88 | ||||
-rw-r--r-- | src/jarabe/journal/listview.py | 54 | ||||
-rw-r--r-- | src/jarabe/journal/misc.py | 95 | ||||
-rw-r--r-- | src/jarabe/journal/modalalert.py | 7 | ||||
-rw-r--r-- | src/jarabe/journal/model.py | 189 | ||||
-rw-r--r-- | src/jarabe/journal/objectchooser.py | 8 | ||||
-rw-r--r-- | src/jarabe/journal/palettes.py | 49 | ||||
-rw-r--r-- | src/jarabe/journal/volumestoolbar.py | 65 |
16 files changed, 453 insertions, 232 deletions
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)} |