# 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 # (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 gettext import gettext as _ from gettext import ngettext import locale import logging import gobject import gtk from sugar.graphics import style from sugar.graphics.icon import Icon, CellRendererIcon from jarabe.controlpanel.sectionview import SectionView from model import UpdateModel _DEBUG_VIEW_ALL = True class ActivityUpdater(SectionView): 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) self._top_label = gtk.Label() self._top_label.set_line_wrap(True) self._top_label.set_justify(gtk.JUSTIFY_LEFT) 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 = gtk.Label() bottom_label.set_line_wrap(True) bottom_label.set_justify(gtk.JUSTIFY_LEFT) bottom_label.props.xalign = 0 bottom_label.set_markup( _('Software updates correct errors, eliminate security ' \ 'vulnerabilities, and provide new features.')) self.pack_start(bottom_label, expand=False) bottom_label.show() self._update_box = None self._progress_pane = None self._refresh() def _switch_to_update_box(self): if self._update_box in self.get_children(): return if self._progress_pane in self.get_children(): self.remove(self._progress_pane) self._progress_pane = None 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) self.pack_start(self._update_box, expand=True, fill=True) self._update_box.show() 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._progress_pane.cancel_button.connect('clicked', self.__cancel_button_clicked_cb) 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(int(current)) 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): logging.debug('ActivityUpdater._finished_checking') 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('%s' % 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('%s' % top_message) self._model.check_updates() def __install_button_clicked_cb(self, button): self._top_label.set_markup('%s' % _('Installing updates...')) self._model.update(self._update_box.get_bundles_to_update()) def __cancel_button_clicked_cb(self, button): self._model.cancel() def _finished_updating(self, installed_updates): logging.debug('ActivityUpdater._finished_updating') 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('%s' % top_message) self._clear_center() def undo(self): self._model.cancel() class ProgressPane(gtk.VBox): '''Container which replaces the `ActivityPane` during refresh or install.''' 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.pack_start(self._label) self._label.show() alignment_box = gtk.Alignment(xalign=0.5, yalign=0.5) self.pack_start(alignment_box) alignment_box.show() self.cancel_button = gtk.Button(stock=gtk.STOCK_CANCEL) alignment_box.add(self.cancel_button) self.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] = '%s\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_string(_('%.0f KB'), size / 1024.0) else: # TRANS: download size of updates, e.g. '2.3 MB' return locale.format_string(_('%.1f MB'), size / 1024.0 / 1024)