Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/extensions/cpsection/updater/view.py
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/cpsection/updater/view.py')
-rw-r--r--extensions/cpsection/updater/view.py652
1 files changed, 321 insertions, 331 deletions
diff --git a/extensions/cpsection/updater/view.py b/extensions/cpsection/updater/view.py
index 0b5062e..9a77743 100644
--- a/extensions/cpsection/updater/view.py
+++ b/extensions/cpsection/updater/view.py
@@ -1,3 +1,6 @@
+# Copyright (C) 2008, One Laptop Per Child
+# Copyright (C) 2009, Tomeu Vizoso
+#
# 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
@@ -12,381 +15,368 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-import logging
-import gtk
-import gobject
-from threading import Thread
from gettext import gettext as _
from gettext import ngettext
+import locale
-from sugar.graphics import style
-from jarabe.controlpanel.sectionview import SectionView
+import gobject
+import gtk
-import model
+from sugar.graphics import style
+from sugar.graphics.icon import Icon, CellRendererIcon
-gtk.gdk.threads_init()
+from jarabe.controlpanel.sectionview import SectionView
-_e = gobject.markup_escape_text
+from model import UpdateModel
_DEBUG_VIEW_ALL = True
+
class ActivityUpdater(SectionView):
- def __init__(self, modelwrapper, alerts):
+
+ def __init__(self, model, alerts):
SectionView.__init__(self)
+
+ self._model = UpdateModel()
+ self._model.connect('progress', self.__progress_cb)
+
self.set_spacing(style.DEFAULT_SPACING)
self.set_border_width(style.DEFAULT_SPACING * 2)
- # top label #
self._top_label = gtk.Label()
self._top_label.set_line_wrap(True)
self._top_label.set_justify(gtk.JUSTIFY_LEFT)
- self._top_label.set_property('xalign', 0)
+ self._top_label.props.xalign = 0
+ self.pack_start(self._top_label, expand=False)
+ self._top_label.show()
+
+ separator = gtk.HSeparator()
+ self.pack_start(separator, expand=False)
+ separator.show()
- # bottom label #
bottom_label = gtk.Label()
bottom_label.set_line_wrap(True)
bottom_label.set_justify(gtk.JUSTIFY_LEFT)
- bottom_label.set_property('xalign', 0)
+ bottom_label.props.xalign = 0
bottom_label.set_markup(
- _('Software updates correct errors, eliminate security' \
+ _('Software updates correct errors, eliminate security ' \
'vulnerabilities, and provide new features.'))
-
- # main canvas #
- self.pack_start(self._top_label, expand=False)
- self.pack_start(gtk.HSeparator(), expand=False)
self.pack_start(bottom_label, expand=False)
+ bottom_label.show()
- # bundle pane #
- self.bundle_list = model.UpdateList()
- self.bundle_pane = BundlePane(self)
- self.pack_start(self.bundle_pane, expand=True)
-
- # progress pane #
- self.progress_pane = ProgressPane(self)
- self.pack_start(self.progress_pane, expand=True, fill=False)
-
- self.show_all()
-
- self.refresh_cb(None, None)
-
- # refresh #
- def refresh_cb(self, widget, event):
- self._top_label.set_markup('<big>%s</big>' % \
- _('Checking for updates...'))
- self.progress_pane.switch_to_check_progress()
- self.bundle_list.freeze_notify()
- Thread(target=self._do_refresh).start()
-
- def _do_refresh(self): #@inhibit_suspend
- self.bundle_list.refresh_list(self._refresh_progress_cb)
- gobject.idle_add(self._refresh_done_cb)
-
- def _refresh_progress_cb(self, n, extra=None):
- gobject.idle_add(self._progress, n, extra)
-
- # refresh done #
- def _refresh_done_cb(self):
- """Invoked in main thread when the refresh is complete."""
- self.bundle_list.thaw_notify()
- avail = self.bundle_list.updates_available()
- if avail == 0:
- header = _("Your software is up-to-date")
- self.progress_pane.switch_to_complete_message()
- else:
- header = ngettext("You can install %s update",
- "You can install %s updates", avail) \
- % avail
- self.bundle_pane.switch()
- self._top_label.set_markup('<big>%s</big>' % _e(header))
- self.bundle_pane.refresh_update_size()
-
- def install_clicked_cb(self, widget, event, data=None):
- """Invoked when the 'ok' button is clicked."""
- self._top_label.set_markup('<big>%s</big>' %
- _('Installing updates...'))
- self.progress_pane.switch_to_download_progress()
- self.bundle_list.freeze_notify()
- Thread(target=self._do_install).start()
-
- def _do_install(self): #@inhibit_suspend
- installed = self.bundle_list.install_updates(self._refresh_progress_cb)
- gobject.idle_add(self._install_done, installed)
-
- def _install_done(self, installed):
- self.bundle_list.thaw_notify()
- header = ngettext("%s update was installed",
- "%s updates were installed", installed) \
- % installed
- self._top_label.set_markup('<big>%s</big>' % _e(header))
- self.progress_pane.update(0)
- self.progress_pane.switch_to_complete_message()
-
- def _progress(self, n, extra=None, icon=None):
- """Invoked in main thread during a refresh operation."""
- self.progress_pane.update(n, extra, icon)
-
- def cancel_cb(self, widget, event, data=None):
- """Callback when the cancel button is clicked."""
- self.bundle_list.cancel()
- self.progress_pane.cancelling()
-
-class BundlePane(gtk.VBox):
- """Container for the activity and group lists."""
-
- def __init__(self, update_activity):
- gtk.VBox.__init__(self)
- self._updater_activity = update_activity
- self.set_spacing(style.DEFAULT_PADDING)
+ self._update_box = None
+ self._progress_pane = None
- # activity list #
- vpaned = gtk.VPaned()
- bundles = BundleListView(update_activity, self)
- vpaned.pack1(bundles, resize=True, shrink=False)
- self.pack_start(vpaned, expand=True)
-
- # Install/refresh buttons #
- button_box = gtk.HBox()
- button_box.set_spacing(style.DEFAULT_SPACING)
- hbox = gtk.HBox()
- hbox.pack_end(button_box, expand=False)
- self._size_label = gtk.Label()
- self._size_label.set_property('xalign', 0)
- self._size_label.set_justify(gtk.JUSTIFY_LEFT)
- hbox.pack_start(self._size_label, expand=True)
- self.pack_end(hbox, expand=False)
- check_button = gtk.Button(stock=gtk.STOCK_REFRESH)
- check_button.connect('clicked', update_activity.refresh_cb, self)
- button_box.pack_start(check_button, expand=False)
-
- self._install_button = _make_button(_("Install selected"),
- name='emblem-downloads')
- self._install_button.connect('clicked',
- update_activity.install_clicked_cb, self)
- button_box.pack_start(self._install_button, expand=False)
-
- def is_valid_cb(bundle_list, __):
- self._install_button.set_sensitive(bundle_list.is_valid())
- update_activity.bundle_list.connect('notify::is-valid', is_valid_cb)
-
- def refresh_update_size(self):
- """Update the 'download size' label."""
- bundle_list = self._updater_activity.bundle_list
- size = bundle_list.updates_size()
- self._size_label.set_markup(_('Download size: %s') %
- model._humanize_size(size))
- self._install_button.set_sensitive(bundle_list.updates_selected()!=0)
-
- def switch(self):
- """Make the bundle list visible and the progress pane invisible."""
- for widget, v in [(self, True),
- (self._updater_activity.progress_pane, False)]:# ,
- #(self.activity_updater.expander, False)]:
- widget.set_property('visible', v)
-
-class BundleListView(gtk.ScrolledWindow):
- """List view at the top, showing activities, versions, and sizes."""
- def __init__(self, update_activity, bundle_pane):
- gtk.ScrolledWindow.__init__(self)
- self._update_activity = update_activity
- self._bundle_pane = bundle_pane
-
- # create TreeView using a filtered treestore
- self._treeview = gtk.TreeView(self._update_activity.bundle_list)
-
- # toggle cell renderers #
- crbool = gtk.CellRendererToggle()
- crbool.set_property('activatable', True)
- crbool.set_property('xpad', style.DEFAULT_PADDING)
- crbool.set_property('indicator_size', style.zoom(26))
- crbool.connect('toggled', self.__toggled_cb)
-
- # icon cell renderers #
- cricon = gtk.CellRendererPixbuf()
- cricon.set_property('width', style.STANDARD_ICON_SIZE)
- cricon.set_property('height', style.STANDARD_ICON_SIZE)
-
- # text cell renderers #
- crtext = gtk.CellRendererText()
- crtext.set_property('xpad', style.DEFAULT_PADDING)
- crtext.set_property('ypad', style.DEFAULT_PADDING)
-
- #create the TreeViewColumn to display the data
- def view_func_maker(propname):
- def view_func(cell_layout, renderer, m, it):
- renderer.set_property(propname,
- not m.get_value(it, model.IS_HEADER))
- return view_func
- hide_func = view_func_maker('visible')
- insens_func = view_func_maker('sensitive')
- column_install = gtk.TreeViewColumn('Install', crbool)
- column_install.add_attribute(crbool, 'active',
- model.UPDATE_SELECTED)
- column_install.set_cell_data_func(crbool, hide_func)
- column = gtk.TreeViewColumn('Name')
- column.pack_start(cricon, expand=False)
- column.pack_start(crtext, expand=True)
- column.add_attribute(cricon, 'pixbuf', model.ICON)
- column.set_resizable(True)
- column.set_cell_data_func(cricon, hide_func)
- def markup_func(cell_layout, renderer, m, it):
- s = '<b>%s</b>' % _e(m.get_value(it, model.NAME))
- if m.get_value(it, model.IS_HEADER):
- s = '<big>%s</big>' % s
- desc = m.get_value(it, model.DESCRIPTION)
- if desc is not None and desc != '':
- s += '\n<small>%s</small>' % _e(desc)
- renderer.set_property('markup', s)
- insens_func(cell_layout, renderer, m, it)
- column.set_cell_data_func(crtext, markup_func)
-
- # add tvcolumn to treeview
- self._treeview.append_column(column_install)
- self._treeview.append_column(column)
-
- self._treeview.set_reorderable(False)
- self._treeview.set_enable_search(False)
- self._treeview.set_headers_visible(False)
- self._treeview.set_rules_hint(True)
- self._treeview.connect('button-press-event',
- self.__button_press_event_cb)
-
- def is_valid_cb(activity_list, __):
- self._treeview.set_sensitive(
- self._update_activity.bundle_list.is_valid())
- self._update_activity.bundle_list.connect('notify::is-valid',
- is_valid_cb)
- is_valid_cb(self._update_activity.bundle_list, None)
-
- self.add_with_viewport(self._treeview)
- self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
-
- def __toggled_cb(self, crbool, path):
- row = self._treeview.props.model[path]
- row[model.UPDATE_SELECTED] = not row[model.UPDATE_SELECTED]
- self._bundle_pane.refresh_update_size()
-
- def __button_press_event_cb(self, widget, event):
- """
- Show a context menu if a right click was performed on an update entry
- """
- if not (event.type == gtk.gdk.BUTTON_PRESS and event.button == 3):
+ self._refresh()
+
+ def _switch_to_update_box(self):
+ if self._update_box in self.get_children():
return
- menu = gtk.Menu()
+ if self._progress_pane in self.get_children():
+ self.remove(self._progress_pane)
+ self._progress_pane = None
- item = gtk.MenuItem(_("_Uncheck All"))
- item.connect("activate", self.__check_activate_cb, False)
- if self._update_activity.bundle_list.updates_available() == 0:
- item.set_property("sensitive", False)
- menu.add(item)
+ if self._update_box is None:
+ self._update_box = UpdateBox(self._model)
+ self._update_box.refresh_button.connect('clicked',
+ self.__refresh_button_clicked_cb)
+ self._update_box.install_button.connect('clicked',
+ self.__install_button_clicked_cb)
- item = gtk.MenuItem(_("_Check All"))
- item.connect("activate", self.__check_activate_cb, True)
- if self._update_activity.bundle_list.updates_available() == 0:
- item.set_property("sensitive", False)
- menu.add(item)
+ self.pack_start(self._update_box, expand=True, fill=True)
+ self._update_box.show()
- menu.popup(None, None, None, 0, event.time)
- menu.show_all()
+ def _switch_to_progress_pane(self):
+ if self._progress_pane in self.get_children():
+ return
+
+ if self._update_box in self.get_children():
+ self.remove(self._update_box)
+ self._update_box = None
+
+ if self._progress_pane is None:
+ self._progress_pane = ProgressPane()
+
+ self.pack_start(self._progress_pane, expand=True, fill=False)
+ self._progress_pane.show()
+
+ def _clear_center(self):
+ if self._progress_pane in self.get_children():
+ self.remove(self._progress_pane)
+ self._progress_pane = None
+
+ if self._update_box in self.get_children():
+ self.remove(self._update_box)
+ self._update_box = None
+
+ def __progress_cb(self, model, action, bundle_name, current, total):
+ if current == total and action == UpdateModel.ACTION_CHECKING:
+ self._finished_checking()
+ return
+ elif current == total:
+ self._finished_updating()
+ return
+
+ if action == UpdateModel.ACTION_CHECKING:
+ message = _('Checking %s...') % bundle_name
+ elif action == UpdateModel.ACTION_DOWNLOADING:
+ message = _('Downloading %s...') % bundle_name
+ elif action == UpdateModel.ACTION_UPDATING:
+ message = _('Updating %s...') % bundle_name
+
+ self._switch_to_progress_pane()
+ self._progress_pane.set_message(message)
+ self._progress_pane.set_progress(current / float(total))
+
+ def _finished_checking(self):
+ available_updates = len(self._model.updates)
+ if not available_updates:
+ top_message = _('Your software is up-to-date')
+ else:
+ top_message = ngettext('You can install %s update',
+ 'You can install %s updates',
+ available_updates)
+ top_message = top_message % available_updates
+ top_message = gobject.markup_escape_text(top_message)
+
+ self._top_label.set_markup('<big>%s</big>' % top_message)
+
+ if not available_updates:
+ self._clear_center()
+ else:
+ self._switch_to_update_box()
+ self._update_box.refresh()
+
+ def __refresh_button_clicked_cb(self, button):
+ self._refresh()
+
+ def _refresh(self):
+ top_message = _('Checking for updates...')
+ self._top_label.set_markup('<big>%s</big>' % top_message)
+ self._model.check_updates()
+
+ def __install_button_clicked_cb(self, button):
+ self._top_label.set_markup('<big>%s</big>' % _('Installing updates...'))
+ self._model.update(self._update_box.get_bundles_to_update())
+
+ def _finished_updating(self):
+ installed_updates = self._model.get_total_bundles_to_update()
+ top_message = ngettext('%s update was installed',
+ '%s updates were installed', installed_updates)
+ top_message = top_message % installed_updates
+ top_message = gobject.markup_escape_text(top_message)
+ self._top_label.set_markup('<big>%s</big>' % top_message)
+ self._clear_center()
- def __check_activate_cb(self, sender, state):
- for i in self._update_activity.bundle_list:
- i[model.UPDATE_SELECTED] = state
- self._bundle_pane.refresh_update_size()
class ProgressPane(gtk.VBox):
- """Container which replaces the `ActivityPane` during refresh or
- install."""
+ '''Container which replaces the `ActivityPane` during refresh or
+ install.'''
- def __init__(self, update_activity):
- self._update_activity = update_activity
+ def __init__(self):
gtk.VBox.__init__(self)
self.set_spacing(style.DEFAULT_PADDING)
self.set_border_width(style.DEFAULT_SPACING * 2)
self._progress = gtk.ProgressBar()
+ self.pack_start(self._progress)
+ self._progress.show()
+
self._label = gtk.Label()
self._label.set_line_wrap(True)
self._label.set_property('xalign', 0.5)
self._label.modify_fg(gtk.STATE_NORMAL,
- style.COLOR_BUTTON_GREY.get_gdk_color())
- self._icon = gtk.Image()
- self._icon.set_property('height-request', style.STANDARD_ICON_SIZE)
- # make an HBox to center the various buttons.
- hbox = gtk.HBox()
- self._cancel_button = gtk.Button(stock=gtk.STOCK_CANCEL)
- self._refresh_button = gtk.Button(stock=gtk.STOCK_REFRESH)
- self._try_again_button = _make_button(_('Try again'),
- stock=gtk.STOCK_REFRESH)
- for widget, cb in [(self._cancel_button, update_activity.cancel_cb),
- (self._refresh_button, update_activity.refresh_cb),
- (self._try_again_button, update_activity.refresh_cb)]:
- widget.connect('clicked', cb, update_activity)
- hbox.pack_start(widget, expand=True, fill=False)
-
- self.pack_start(self._icon)
- self.pack_start(self._progress)
+ style.COLOR_BUTTON_GREY.get_gdk_color())
self.pack_start(self._label)
- self.pack_start(hbox)
+ self._label.show()
- def update(self, n, extra=None, icon=None):
- """Update the status of the progress pane. `n` should be a float
- in [0, 1], or else None. You can optionally provide extra information
- in `extra` or an icon in `icon`."""
+ alignment_box = gtk.Alignment(xalign=0.5, yalign=0.5)
+ self.pack_start(alignment_box)
+ alignment_box.show()
- if n is None:
- self._progress.pulse()
- else:
- self._progress.set_fraction(n)
- extra = _e(extra) if extra is not None else ''
- self._label.set_markup(extra)
- self._icon.set_property('visible', icon is not None)
- if icon is not None:
- self._icon.set_from_pixbuf(icon)
-
- def switch_to_check_progress(self):
- self._switch(show_cancel=True, show_bar=True)
- self._label.set_markup(_('Checking for updates...'))
-
- def switch_to_download_progress(self):
- self._switch(show_cancel=True, show_bar=True)
- self._label.set_markup(_('Starting download...'))
-
- def switch_to_complete_message(self):
- self._switch(show_cancel=False, show_bar=False)
- self._label.set_markup('')
-
- def cancelling(self):
- self._cancel_button.set_sensitive(False)
- self._label.set_markup(_('Cancelling...'))
-
- def _switch(self, show_cancel, show_bar, show_try_again=False):
- """Make the progress pane visible and the activity pane invisible."""
- self._update_activity.bundle_pane.set_property('visible', False)
- self.set_property('visible', True)
- for widget, v in [(self._progress, show_bar),
- (self._cancel_button, show_cancel),
- (self._refresh_button,
- not show_cancel and not show_try_again),
- (self._try_again_button, show_try_again),
- #(self._update_activity.expander, False)
- ]:
- widget.set_property('visible', v)
- self._cancel_button.set_sensitive(True)
- #self._update_activity.expander.set_expanded(False)
-
-def _make_button(label_text, stock=None, name=None):
- """Convenience function to make labelled buttons with images."""
- b = gtk.Button()
- hbox = gtk.HBox()
- hbox.set_spacing(style.DEFAULT_PADDING)
- i = gtk.Image()
- if stock is not None:
- i.set_from_stock(stock, gtk.ICON_SIZE_BUTTON)
- if name is not None:
- i.set_from_icon_name(name, gtk.ICON_SIZE_BUTTON)
- hbox.pack_start(i, expand=False)
- l = gtk.Label(label_text)
- hbox.pack_start(l, expand=False)
- b.add(hbox)
- return b
+ cancel_button = gtk.Button(stock=gtk.STOCK_CANCEL)
+ alignment_box.add(cancel_button)
+ cancel_button.show()
+
+ def set_message(self, message):
+ self._label.set_text(message)
+
+ def set_progress(self, fraction):
+ self._progress.props.fraction = fraction
+
+
+class UpdateBox(gtk.VBox):
+
+ def __init__(self, model):
+ gtk.VBox.__init__(self)
+
+ self._model = model
+
+ self.set_spacing(style.DEFAULT_PADDING)
+
+ scrolled_window = gtk.ScrolledWindow()
+ scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.pack_start(scrolled_window)
+ scrolled_window.show()
+
+ self._update_list = UpdateList(model)
+ self._update_list.props.model.connect('row-changed',
+ self.__row_changed_cb)
+ scrolled_window.add(self._update_list)
+ self._update_list.show()
+
+ bottom_box = gtk.HBox()
+ bottom_box.set_spacing(style.DEFAULT_SPACING)
+ self.pack_start(bottom_box, expand=False)
+ bottom_box.show()
+
+ self._size_label = gtk.Label()
+ self._size_label.props.xalign = 0
+ self._size_label.set_justify(gtk.JUSTIFY_LEFT)
+ bottom_box.pack_start(self._size_label, expand=True)
+ self._size_label.show()
+
+ self.refresh_button = gtk.Button(stock=gtk.STOCK_REFRESH)
+ bottom_box.pack_start(self.refresh_button, expand=False)
+ self.refresh_button.show()
+
+ self.install_button = gtk.Button(_('Install selected'))
+ self.install_button.props.image = Icon(icon_name='emblem-downloads',
+ icon_size=gtk.ICON_SIZE_BUTTON)
+ bottom_box.pack_start(self.install_button, expand=False)
+ self.install_button.show()
+
+ self._update_total_size_label()
+
+ def refresh(self):
+ self._update_list.refresh()
+
+ def __row_changed_cb(self, list_model, path, iterator):
+ self._update_total_size_label()
+ self._update_install_button()
+
+ def _update_total_size_label(self):
+ total_size = 0
+ for row in self._update_list.props.model:
+ if row[UpdateListModel.SELECTED]:
+ total_size += row[UpdateListModel.SIZE]
+
+ markup = _('Download size: %s') % _format_size(total_size)
+ self._size_label.set_markup(markup)
+
+ def _update_install_button(self):
+ for row in self._update_list.props.model:
+ if row[UpdateListModel.SELECTED]:
+ self.install_button.props.sensitive = True
+ return
+ self.install_button.props.sensitive = False
+
+ def get_bundles_to_update(self):
+ bundles_to_update = []
+ for row in self._update_list.props.model:
+ if row[UpdateListModel.SELECTED]:
+ bundles_to_update.append(row[UpdateListModel.BUNDLE_ID])
+ return bundles_to_update
+
+
+class UpdateList(gtk.TreeView):
+
+ def __init__(self, model):
+ list_model = UpdateListModel(model)
+ gtk.TreeView.__init__(self, list_model)
+
+ self.set_reorderable(False)
+ self.set_enable_search(False)
+ self.set_headers_visible(False)
+
+ toggle_renderer = gtk.CellRendererToggle()
+ toggle_renderer.props.activatable = True
+ toggle_renderer.props.xpad = style.DEFAULT_PADDING
+ toggle_renderer.props.indicator_size = style.zoom(26)
+ toggle_renderer.connect('toggled', self.__toggled_cb)
+
+ toggle_column = gtk.TreeViewColumn()
+ toggle_column.pack_start(toggle_renderer)
+ toggle_column.add_attribute(toggle_renderer, 'active',
+ UpdateListModel.SELECTED)
+ self.append_column(toggle_column)
+
+ icon_renderer = CellRendererIcon(self)
+ icon_renderer.props.width = style.STANDARD_ICON_SIZE
+ icon_renderer.props.height = style.STANDARD_ICON_SIZE
+ icon_renderer.props.size = style.STANDARD_ICON_SIZE
+ icon_renderer.props.xpad = style.DEFAULT_PADDING
+ icon_renderer.props.ypad = style.DEFAULT_PADDING
+ icon_renderer.props.stroke_color = style.COLOR_TOOLBAR_GREY.get_svg()
+ icon_renderer.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
+
+ icon_column = gtk.TreeViewColumn()
+ icon_column.pack_start(icon_renderer)
+ icon_column.add_attribute(icon_renderer, 'file-name',
+ UpdateListModel.ICON_FILE_NAME)
+ self.append_column(icon_column)
+
+ text_renderer = gtk.CellRendererText()
+
+ description_column = gtk.TreeViewColumn()
+ description_column.pack_start(text_renderer)
+ description_column.add_attribute(text_renderer, 'markup',
+ UpdateListModel.DESCRIPTION)
+ self.append_column(description_column)
+
+ def __toggled_cb(self, cell_renderer, path):
+ row = self.props.model[path]
+ row[UpdateListModel.SELECTED] = not row[UpdateListModel.SELECTED]
+
+ def refresh(self):
+ pass
+
+
+class UpdateListModel(gtk.ListStore):
+
+ BUNDLE_ID = 0
+ SELECTED = 1
+ ICON_FILE_NAME = 2
+ DESCRIPTION = 3
+ SIZE = 4
+
+ def __init__(self, model):
+ gtk.ListStore.__init__(self, str, bool, str, str, int)
+
+ for bundle_update in model.updates:
+ row = [None] * 5
+ row[self.BUNDLE_ID] = bundle_update.bundle.get_bundle_id()
+ row[self.SELECTED] = True
+ row[self.ICON_FILE_NAME] = bundle_update.bundle.get_icon()
+
+ details = _('From version %(current)d to %(new)s (Size: %(size)s)')
+ details = details % \
+ {'current': bundle_update.bundle.get_activity_version(),
+ 'new': bundle_update.version,
+ 'size': _format_size(bundle_update.size)}
+
+ row[self.DESCRIPTION] = '<b>%s</b>\n%s' % \
+ (bundle_update.bundle.get_name(), details)
+
+ row[self.SIZE] = bundle_update.size
+
+ self.append(row)
+
+
+def _format_size(size):
+ '''
+ Convert a given size in bytes to a nicer better readable unit
+ '''
+ if size == 0:
+ # TRANS: download size is 0
+ return _('None')
+ elif size < 1024:
+ # TRANS: download size of very small updates
+ return _('1 KB')
+ elif size < 1024 * 1024:
+ # TRANS: download size of small updates, e.g. '250 KB'
+ return locale.format(_('%.0f KB'), size / 1024.0)
+ else:
+ # TRANS: download size of updates, e.g. '2.3 MB'
+ return locale.format(_('%.1f MB'), size / 1024.0 / 1024)