def __init__(self, selected_callback, cancel_callback): self._selected_callback = selected_callback self._cancel_callback = cancel_callback self.total_contracts = 0 self.contract_selection_xml = gtk.glade.XML(CONTRACT_SELECTION_GLADE) self.contract_selection_win = self.contract_selection_xml.get_widget( "contract_selection_window") self.subscribe_button = self.contract_selection_xml.get_widget( 'subscribe_button') self.edit_quantity_label = self.contract_selection_xml.get_widget( 'edit_quantity_label') self.contract_selection_treeview = \ self.contract_selection_xml.get_widget( "contract_selection_treeview") self.contract_selection_treeview.get_selection().connect( "changed", self._on_contract_selection) self.subscription_name_label = self.contract_selection_xml.get_widget( "subscription_name_label") self.total_contracts_label = self.contract_selection_xml.get_widget( "total_contracts_label") self.contract_selection_xml.signal_autoconnect({ "on_cancel_button_clicked": self._cancel_button_clicked, "on_subscribe_button_clicked": self._subscribe_button_clicked, }) self.model = MappedListStore(self.get_type_map()) self.contract_selection_treeview.set_model(self.model)
def __init__(self, selected_callback, cancel_callback): self._selected_callback = selected_callback self._cancel_callback = cancel_callback self.total_contracts = 0 self.contract_selection_xml = gtk.glade.XML(CONTRACT_SELECTION_GLADE) self.contract_selection_win = self.contract_selection_xml.get_widget( "contract_selection_window") self.subscribe_button = self.contract_selection_xml.get_widget('subscribe_button') self.edit_quantity_label = self.contract_selection_xml.get_widget('edit_quantity_label') self.contract_selection_treeview = \ self.contract_selection_xml.get_widget( "contract_selection_treeview") self.contract_selection_treeview.get_selection().connect("changed", self._on_contract_selection) self.subscription_name_label = self.contract_selection_xml.get_widget( "subscription_name_label") self.total_contracts_label = self.contract_selection_xml.get_widget( "total_contracts_label") self.contract_selection_xml.signal_autoconnect({ "on_cancel_button_clicked": self._cancel_button_clicked, "on_subscribe_button_clicked": self._subscribe_button_clicked, }) self.model = MappedListStore(self.get_type_map()) self.contract_selection_treeview.set_model(self.model)
def __init__(self, selected_callback, cancel_callback): super(ContractSelectionWindow, self).__init__() self._selected_callback = selected_callback self._cancel_callback = cancel_callback self.total_contracts = 0 self.contract_selection_treeview.get_selection().connect("changed", self._on_contract_selection) self.subscription_name_label.set_line_wrap(True) callbacks = {"on_cancel_button_clicked": self._cancel_button_clicked, "size-allocate": lambda label, size: label.set_size_request(size.width - 1, -1), "on_subscribe_button_clicked": self._subscribe_button_clicked} self.connect_signals(callbacks) self.model = MappedListStore(self.get_type_map()) self.contract_selection_treeview.set_model(self.model)
class ContractSelectionWindow(object): def __init__(self, selected_callback, cancel_callback): self._selected_callback = selected_callback self._cancel_callback = cancel_callback self.total_contracts = 0 self.contract_selection_xml = gtk.glade.XML(CONTRACT_SELECTION_GLADE) self.contract_selection_win = self.contract_selection_xml.get_widget( "contract_selection_window") self.subscribe_button = self.contract_selection_xml.get_widget('subscribe_button') self.edit_quantity_label = self.contract_selection_xml.get_widget('edit_quantity_label') self.contract_selection_treeview = \ self.contract_selection_xml.get_widget( "contract_selection_treeview") self.contract_selection_treeview.get_selection().connect("changed", self._on_contract_selection) self.subscription_name_label = self.contract_selection_xml.get_widget( "subscription_name_label") self.total_contracts_label = self.contract_selection_xml.get_widget( "total_contracts_label") self.contract_selection_xml.signal_autoconnect({ "on_cancel_button_clicked": self._cancel_button_clicked, "on_subscribe_button_clicked": self._subscribe_button_clicked, }) self.model = MappedListStore(self.get_type_map()) self.contract_selection_treeview.set_model(self.model) def get_type_map(self): return { 'contract_number': str, 'consumed_fraction': str, 'start_date': gobject.TYPE_PYOBJECT, 'end_date': gobject.TYPE_PYOBJECT, 'default_quantity': int, 'product_name': str, 'pool': gobject.TYPE_PYOBJECT, 'is_virt_only': bool, 'multi_entitlement': bool, 'quantity_available': int, 'quantity_increment': int, } def show(self): self.populate_treeview() self.contract_selection_win.show_all() def destroy(self): self.contract_selection_win.destroy() def populate_treeview(self): renderer = gtk.CellRendererText() column = gtk.TreeViewColumn(_("Contract"), renderer, text=self.model['contract_number']) column.set_expand(True) column.set_sort_column_id(self.model['contract_number']) self.model.set_sort_func(self.model['contract_number'], self._sort_text, None) self.contract_selection_treeview.append_column(column) column = widgets.MachineTypeColumn(self.model['is_virt_only']) column.set_sort_column_id(self.model['is_virt_only']) self.model.set_sort_func(self.model['is_virt_only'], self._sort_machine_type, column) self.contract_selection_treeview.append_column(column) renderer = gtk.CellRendererText() renderer.set_property("xalign", 0.5) column = gtk.TreeViewColumn(_("Used / Total"), renderer, text=self.model['consumed_fraction']) self.contract_selection_treeview.append_column(column) renderer = widgets.CellRendererDate() column = gtk.TreeViewColumn(_("Start Date"), renderer, date=self.model['start_date']) column.set_sort_column_id(self.model['start_date']) self.model.set_sort_func(self.model['start_date'], self._sort_date, None) self.contract_selection_treeview.append_column(column) renderer = widgets.CellRendererDate() column = gtk.TreeViewColumn(_("End Date"), renderer, date=self.model['end_date']) column.set_sort_column_id(self.model['end_date']) self.model.set_sort_func(self.model['end_date'], self._sort_date, None) self.contract_selection_treeview.append_column(column) column = widgets.QuantitySelectionColumn(_("Quantity"), self.model, self.model['default_quantity'], self.model['multi_entitlement'], self.model['quantity_available'], self.model['quantity_increment']) self.contract_selection_treeview.append_column(column) self.edit_quantity_label.set_label(column.get_column_legend_text()) def add_pool(self, pool, default_quantity_value): self.total_contracts += 1 self.total_contracts_label.set_text(str(self.total_contracts)) self.subscription_name_label.set_text(pool['productName']) # Use unlimited for -1 quanities quantity = pool['quantity'] if quantity < 0: quantity = _('Unlimited') quantity_available = -1 else: quantity_available = int(pool['quantity']) - int(pool['consumed']) # cap the default selected quantity at the max available # for that pool. See #855257. Watch out for quantity_available # being -1 (unlimited). if default_quantity_value > quantity_available and quantity_available >= 0: default_quantity_value = quantity_available quantity_increment = 1 if 'calculatedAttributes' in pool: calculated_attrs = pool['calculatedAttributes'] if 'quantity_increment' in calculated_attrs: quantity_increment = int(calculated_attrs['quantity_increment']) self.model.add_map({ 'contract_number': pool['contractNumber'], 'consumed_fraction': "%s / %s" % (pool['consumed'], quantity), 'start_date': isodate.parse_date(pool['startDate']), 'end_date': isodate.parse_date(pool['endDate']), 'default_quantity': default_quantity_value, 'product_name': pool['productName'], 'pool': pool, 'is_virt_only': PoolWrapper(pool).is_virt_only(), 'multi_entitlement': allows_multi_entitlement(pool), 'quantity_available': quantity_available, 'quantity_increment': quantity_increment, }) def set_parent_window(self, window): self.contract_selection_win.set_transient_for(window) def _cancel_button_clicked(self, button): self._cancel_callback() def _subscribe_button_clicked(self, button): selection = self.contract_selection_treeview.get_selection() model, iter = selection.get_selected() if iter is not None: pool, quantity = model.get(iter, model['pool'], model['default_quantity']) self._selected_callback(pool, quantity) def _on_contract_selection(self, widget): model, tree_iter = widget.get_selected() enabled = True if not tree_iter: enabled = False self.subscribe_button.set_sensitive(enabled) def _sort_text(self, model, row1, row2, data): sort_column, sort_type = model.get_sort_column_id() str1 = model.get_value(row1, sort_column) str2 = model.get_value(row2, sort_column) return cmp(str1, str2) def _sort_machine_type(self, model, row1, row2, col): # Machine type is actually a boolean denoting whether the type is # virtual or not. We do not want to sort on the boolean value. # Instead we want to sort on the text value that is going to get # displayed for the boolean value. sort_column, sort_type = model.get_sort_column_id() results = [] for row in [row1, row2]: _bool = model.get_value(row, sort_column) if _bool is None: text = col._get_none_text() elif bool(_bool): text = col._get_true_text() else: text = col._get_false_text() results.append(text) return cmp(results[0], results[1]) def _sort_date(self, model, row1, row2, data): """ Used for sorting dates that could be None. """ sort_column, sort_type = model.get_sort_column_id() date1 = model.get_value(row1, sort_column) \ or datetime.date(datetime.MINYEAR, 1, 1) date2 = model.get_value(row2, sort_column) \ or datetime.date(datetime.MINYEAR, 1, 1) epoch1 = time.mktime(date1.timetuple()) epoch2 = time.mktime(date2.timetuple()) return cmp(epoch1, epoch2)
def __init__(self, backend, parent): super(RepositoriesDialog, self).__init__('repositories.glade') # Set up dynamic elements self.overrides_treeview = gtk.TreeView() # Add at-spi because we no longer create this widget from glade self.overrides_treeview.get_accessible().set_name("Repository View") self.no_repos_label, self.no_repos_label_viewport = widgets.get_scrollable_label() self.widget_switcher = widgets.WidgetSwitcher(self.scrolledwindow, self.no_repos_label_viewport, self.overrides_treeview) # We require the backend here so that we can always use its version # of Overrides which will guarantee that the CP UEPConnection is up # to date. # FIXME: We really shouldn't have to worry about our connection info # changing out from under us. self.backend = backend self.identity = require(IDENTITY) self.ent_dir = require(ENT_DIR) self.glade.signal_autoconnect({ "on_dialog_delete_event": self._on_close, "on_close_button_clicked": self._on_close, "on_reset_button_clicked": self._on_reset_repo, }) self.overrides_store = MappedListStore({ "repo_id": str, "enabled": bool, "modified": bool, "modified-icon": gtk.gdk.Pixbuf, "name": str, "baseurl": str, "gpgcheck": bool, "gpgcheck_modified": bool, "repo_data": object, "override_data": object }) self.other_overrides = OverridesTable(self.other_overrides_view) # Change the background color of the no_repos_label_container to the same color # as the label's base color. The event container allows us to change the color. label_base_color = self.no_repos_label.style.base[gtk.STATE_NORMAL] self.no_repos_label_viewport.modify_bg(gtk.STATE_NORMAL, label_base_color) self.overrides_treeview.set_model(self.overrides_store) self.modified_icon = self.overrides_treeview.render_icon(gtk.STOCK_APPLY, gtk.ICON_SIZE_MENU) sortable_cols = [] enabled_col = CheckBoxColumn(_("Enabled"), self.overrides_store, 'enabled', self._on_enable_repo_toggle) self.overrides_treeview.append_column(enabled_col) gpgcheck_col = CheckBoxColumn(_("Gpgcheck"), self.overrides_store, 'gpgcheck', self._on_gpgcheck_toggle_changed) self.overrides_treeview.append_column(gpgcheck_col) repo_id_col = TextTreeViewColumn(self.overrides_store, _("Repository ID"), 'repo_id', expand=True) self.overrides_treeview.append_column(repo_id_col) sortable_cols.append((repo_id_col, 'text', 'repo_id')) modified_col = gtk.TreeViewColumn(_("Modified"), gtk.CellRendererPixbuf(), pixbuf=self.overrides_store['modified-icon']) self.overrides_treeview.append_column(modified_col) sortable_cols.append((modified_col, 'text', 'modified')) self.set_sorts(self.overrides_store, sortable_cols) self.overrides_treeview.get_selection().connect('changed', self._on_selection) self.overrides_treeview.set_rules_hint(True) self.main_window.set_transient_for(parent)
class RepositoriesDialog(widgets.GladeWidget, HasSortableWidget): """ GTK dialog for managing repositories and their overrides. """ widget_names = ['main_window', 'reset_button', 'close_button', 'name_text', 'baseurl_text', 'scrolledwindow', 'other_overrides_view'] ENTS_PROVIDE_NO_REPOS = _("Attached subscriptions do not provide any repositories.") NO_ATTACHED_SUBS = _("No repositories are available without an attached subscription.") def __init__(self, backend, parent): super(RepositoriesDialog, self).__init__('repositories.glade') # Set up dynamic elements self.overrides_treeview = gtk.TreeView() # Add at-spi because we no longer create this widget from glade self.overrides_treeview.get_accessible().set_name("Repository View") self.no_repos_label, self.no_repos_label_viewport = widgets.get_scrollable_label() self.widget_switcher = widgets.WidgetSwitcher(self.scrolledwindow, self.no_repos_label_viewport, self.overrides_treeview) # We require the backend here so that we can always use its version # of Overrides which will guarantee that the CP UEPConnection is up # to date. # FIXME: We really shouldn't have to worry about our connection info # changing out from under us. self.backend = backend self.identity = require(IDENTITY) self.ent_dir = require(ENT_DIR) self.glade.signal_autoconnect({ "on_dialog_delete_event": self._on_close, "on_close_button_clicked": self._on_close, "on_reset_button_clicked": self._on_reset_repo, }) self.overrides_store = MappedListStore({ "repo_id": str, "enabled": bool, "modified": bool, "modified-icon": gtk.gdk.Pixbuf, "name": str, "baseurl": str, "gpgcheck": bool, "gpgcheck_modified": bool, "repo_data": object, "override_data": object }) self.other_overrides = OverridesTable(self.other_overrides_view) # Change the background color of the no_repos_label_container to the same color # as the label's base color. The event container allows us to change the color. label_base_color = self.no_repos_label.style.base[gtk.STATE_NORMAL] self.no_repos_label_viewport.modify_bg(gtk.STATE_NORMAL, label_base_color) self.overrides_treeview.set_model(self.overrides_store) self.modified_icon = self.overrides_treeview.render_icon(gtk.STOCK_APPLY, gtk.ICON_SIZE_MENU) sortable_cols = [] enabled_col = CheckBoxColumn(_("Enabled"), self.overrides_store, 'enabled', self._on_enable_repo_toggle) self.overrides_treeview.append_column(enabled_col) gpgcheck_col = CheckBoxColumn(_("Gpgcheck"), self.overrides_store, 'gpgcheck', self._on_gpgcheck_toggle_changed) self.overrides_treeview.append_column(gpgcheck_col) repo_id_col = TextTreeViewColumn(self.overrides_store, _("Repository ID"), 'repo_id', expand=True) self.overrides_treeview.append_column(repo_id_col) sortable_cols.append((repo_id_col, 'text', 'repo_id')) modified_col = gtk.TreeViewColumn(_("Modified"), gtk.CellRendererPixbuf(), pixbuf=self.overrides_store['modified-icon']) self.overrides_treeview.append_column(modified_col) sortable_cols.append((modified_col, 'text', 'modified')) self.set_sorts(self.overrides_store, sortable_cols) self.overrides_treeview.get_selection().connect('changed', self._on_selection) self.overrides_treeview.set_rules_hint(True) self.main_window.set_transient_for(parent) def hide(self): self.main_window.hide() def show(self): self._load_data() self.main_window.present() def _load_data(self): # pull the latest overrides from the cache which will be the ones from the server. current_overrides = self.backend.overrides.get_overrides(self.identity.uuid) or [] self._refresh(current_overrides) # By default sort by repo_id self.overrides_store.set_sort_column_id(0, gtk.SORT_ASCENDING) def _refresh(self, current_overrides, repo_id_to_select=None): overrides_per_repo = {} for override in current_overrides: repo_id = override.repo_id overrides_per_repo.setdefault(repo_id, {}) overrides_per_repo[repo_id][override.name] = override.value self.overrides_store.clear() self.other_overrides.clear() current_repos = self.backend.overrides.repo_lib.get_repos(apply_overrides=False) if (current_repos): self.widget_switcher.set_active(1) else: ent_count = len(self.ent_dir.list_valid()) no_repos_message = self.ENTS_PROVIDE_NO_REPOS if ent_count == 0: no_repos_message = self.NO_ATTACHED_SUBS self.no_repos_label.set_markup("<b>%s</b>" % no_repos_message) self.widget_switcher.set_active(0) # Fetch the repositories from repolib without any overrides applied. # We do this so that we can tell if anything has been modified by # overrides. for repo in current_repos: overrides = overrides_per_repo.get(repo.id, None) modified = not overrides is None enabled = self._get_boolean(self._get_model_value(repo, overrides, 'enabled')[0]) gpgcheck, gpgcheck_modified = self._get_model_value(repo, overrides, 'gpgcheck') gpgcheck = self._get_boolean(gpgcheck) self.overrides_store.add_map({ 'enabled': bool(int(enabled)), 'repo_id': repo.id, 'modified': modified, 'modified-icon': self._get_modified_icon(modified), 'name': repo['name'], 'baseurl': repo['baseurl'], 'gpgcheck': gpgcheck, 'gpgcheck_modified': gpgcheck_modified, 'repo_data': repo, 'override_data': overrides }) first_row_iter = self.overrides_store.get_iter_first() if not first_row_iter: self.reset_button.set_sensitive(False) elif repo_id_to_select: self._select_by_repo_id(repo_id_to_select) else: self.overrides_treeview.get_selection().select_iter(first_row_iter) def _get_modified_icon(self, modified): icon = None if modified: icon = self.modified_icon return icon def _get_selected_repo_id(self): selected = None override_selection = SelectionWrapper(self.overrides_treeview.get_selection(), self.overrides_store) if override_selection.is_valid(): selected = override_selection['repo_id'] return selected def _select_by_repo_id(self, repo_id): repo_data = (repo_id, self.overrides_store['repo_id']) self.overrides_store.foreach(self._select_repo_row, repo_data) def _select_repo_row(self, model, path, tree_iter, repo_data_tuple): """ Passed to model's foreach method to select the row if the repo_id matches. Returning True tells foreach to stop processing rows. """ repo_id, check_idx = repo_data_tuple row_repo_id = model.get_value(tree_iter, check_idx) if repo_id == row_repo_id: self.overrides_treeview.get_selection().select_iter(tree_iter) return True return False def _get_boolean(self, override_value): # An override value might come in as an int or a boolean string. # Try our best to convert it, and default to 0. try: val = int(override_value) except ValueError: val = 0 if override_value is not None and override_value.upper() == "TRUE": val = 1 return bool(val) def _get_model_value(self, repo, overrides, property_name): if not overrides or not property_name in overrides: return (repo[property_name], False) return (overrides[property_name], True) def _on_reset_repo(self, button): selection = SelectionWrapper(self.overrides_treeview.get_selection(), self.overrides_store) if not selection.is_valid(): return confirm = YesNoDialog(_("Are you sure you want to remove all overrides for <b>%s</b>?") % selection['repo_id'], self._get_dialog_widget(), _("Confirm Remove All Overrides")) confirm.connect("response", self._on_reset_repo_response) def _on_reset_repo_response(self, dialog, response): if not response: return selection = SelectionWrapper(self.overrides_treeview.get_selection(), self.overrides_store) if not selection.is_valid(): return repo_id = selection['repo_id'] try: self._delete_all_overrides(repo_id) except Exception, e: handle_gui_exception(e, _("Unable to reset repository overrides."), self._get_dialog_widget())
def __init__(self, backend, parent): super(RepositoriesDialog, self).__init__() # Label wrapping on resize does not work very well on GTK2+ # so we resize on the fly. if gtk_compat.GTK_COMPAT_VERSION == "2": # Set the title to wrap and connect to size-allocate to # properly resize the label so that it takes up the most # space it can. self.description_text.set_line_wrap(True) self.description_text.connect( 'size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1)) self.name_text.set_line_wrap(True) self.name_text.connect( 'size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1)) self.baseurl_text.set_line_wrap(True) self.baseurl_text.connect( 'size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1)) # Set up dynamic elements self.overrides_treeview = ga_Gtk.TreeView() # Add at-spi because we no longer create this widget from glade self.overrides_treeview.get_accessible().set_name("Repository View") self.no_repos_label, self.no_repos_label_viewport = widgets.get_scrollable_label( ) self.widget_switcher = widgets.WidgetSwitcher( self.scrolledwindow, self.no_repos_label_viewport, self.overrides_treeview) # We require the backend here so that we can always use its version # of Overrides which will guarantee that the CP UEPConnection is up # to date. # FIXME: We really shouldn't have to worry about our connection info # changing out from under us. self.backend = backend self.async_update = AsyncRepoOverridesUpdate(self.backend.overrides) self.identity = require(IDENTITY) self.ent_dir = require(ENT_DIR) self.connect_signals({ "on_dialog_delete_event": self._on_close, "on_close_button_clicked": self._on_close, "on_apply_button_clicked": self._on_apply_request, "on_reset_button_clicked": self._on_reset_repo }) self.overrides_store = MappedListStore({ "repo_id": str, "enabled": bool, "modified": bool, "modified-icon": ga_GdkPixbuf.Pixbuf, "name": str, "baseurl": str, "gpgcheck": bool, "gpgcheck_modified": bool, "repo_data": object, "override_data": object }) self.other_overrides = OverridesTable(self.other_overrides_view) # FIXME: think this needs get_style_context() and possible a # Gtk.StyleProvider for gtk3 # Change the background color of the no_repos_label_container to the same color # as the label's base color. The event container allows us to change the color. #label_base_color = self.no_repos_label.get_style_context().base[Gtk.StateType.NORMAL] #self.no_repos_label_viewport.modify_bg(Gtk.StateType.NORMAL, label_base_color) self.overrides_treeview.set_model(self.overrides_store) self.modified_icon = self.overrides_treeview.render_icon( ga_Gtk.STOCK_APPLY, ga_Gtk.IconSize.MENU) sortable_cols = [] enabled_col = CheckBoxColumn(_("Enabled"), self.overrides_store, 'enabled', self._on_enable_repo_toggle) self.overrides_treeview.append_column(enabled_col) gpgcheck_col = CheckBoxColumn(_("Gpgcheck"), self.overrides_store, 'gpgcheck', self._on_gpgcheck_toggle_changed) self.overrides_treeview.append_column(gpgcheck_col) repo_id_col = TextTreeViewColumn(self.overrides_store, _("Repository ID"), 'repo_id', expand=True) self.overrides_treeview.append_column(repo_id_col) sortable_cols.append((repo_id_col, 'text', 'repo_id')) modified_col = ga_Gtk.TreeViewColumn( _("Modified"), ga_Gtk.CellRendererPixbuf(), pixbuf=self.overrides_store['modified-icon']) self.overrides_treeview.append_column(modified_col) sortable_cols.append((modified_col, 'text', 'modified')) self.set_sorts(self.overrides_store, sortable_cols) self.overrides_treeview.get_selection().connect( 'changed', self._on_selection) self.overrides_treeview.set_rules_hint(True) # Progress bar self.pb = None self.timer = 0 self.parent = parent self.main_window.set_transient_for(parent)
class RepositoriesDialog(widgets.SubmanBaseWidget, HasSortableWidget): """ GTK dialog for managing repositories and their overrides. """ widget_names = [ 'main_window', 'reset_button', 'close_button', 'apply_button', 'name_text', 'baseurl_text', 'scrolledwindow', 'other_overrides_view', 'details_vbox', 'main_content_container', 'description_text' ] gui_file = "repositories" ENTS_PROVIDE_NO_REPOS = _( "Attached subscriptions do not provide any repositories.") NO_ATTACHED_SUBS = _( "No repositories are available without an attached subscription.") REPOS_DISABLED_BY_CFG = _("Repositories disabled by configuration.") def __init__(self, backend, parent): super(RepositoriesDialog, self).__init__() # Label wrapping on resize does not work very well on GTK2+ # so we resize on the fly. if gtk_compat.GTK_COMPAT_VERSION == "2": # Set the title to wrap and connect to size-allocate to # properly resize the label so that it takes up the most # space it can. self.description_text.set_line_wrap(True) self.description_text.connect( 'size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1)) self.name_text.set_line_wrap(True) self.name_text.connect( 'size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1)) self.baseurl_text.set_line_wrap(True) self.baseurl_text.connect( 'size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1)) # Set up dynamic elements self.overrides_treeview = ga_Gtk.TreeView() # Add at-spi because we no longer create this widget from glade self.overrides_treeview.get_accessible().set_name("Repository View") self.no_repos_label, self.no_repos_label_viewport = widgets.get_scrollable_label( ) self.widget_switcher = widgets.WidgetSwitcher( self.scrolledwindow, self.no_repos_label_viewport, self.overrides_treeview) # We require the backend here so that we can always use its version # of Overrides which will guarantee that the CP UEPConnection is up # to date. # FIXME: We really shouldn't have to worry about our connection info # changing out from under us. self.backend = backend self.async_update = AsyncRepoOverridesUpdate(self.backend.overrides) self.identity = require(IDENTITY) self.ent_dir = require(ENT_DIR) self.connect_signals({ "on_dialog_delete_event": self._on_close, "on_close_button_clicked": self._on_close, "on_apply_button_clicked": self._on_apply_request, "on_reset_button_clicked": self._on_reset_repo }) self.overrides_store = MappedListStore({ "repo_id": str, "enabled": bool, "modified": bool, "modified-icon": ga_GdkPixbuf.Pixbuf, "name": str, "baseurl": str, "gpgcheck": bool, "gpgcheck_modified": bool, "repo_data": object, "override_data": object }) self.other_overrides = OverridesTable(self.other_overrides_view) # FIXME: think this needs get_style_context() and possible a # Gtk.StyleProvider for gtk3 # Change the background color of the no_repos_label_container to the same color # as the label's base color. The event container allows us to change the color. #label_base_color = self.no_repos_label.get_style_context().base[Gtk.StateType.NORMAL] #self.no_repos_label_viewport.modify_bg(Gtk.StateType.NORMAL, label_base_color) self.overrides_treeview.set_model(self.overrides_store) self.modified_icon = self.overrides_treeview.render_icon( ga_Gtk.STOCK_APPLY, ga_Gtk.IconSize.MENU) sortable_cols = [] enabled_col = CheckBoxColumn(_("Enabled"), self.overrides_store, 'enabled', self._on_enable_repo_toggle) self.overrides_treeview.append_column(enabled_col) gpgcheck_col = CheckBoxColumn(_("Gpgcheck"), self.overrides_store, 'gpgcheck', self._on_gpgcheck_toggle_changed) self.overrides_treeview.append_column(gpgcheck_col) repo_id_col = TextTreeViewColumn(self.overrides_store, _("Repository ID"), 'repo_id', expand=True) self.overrides_treeview.append_column(repo_id_col) sortable_cols.append((repo_id_col, 'text', 'repo_id')) modified_col = ga_Gtk.TreeViewColumn( _("Modified"), ga_Gtk.CellRendererPixbuf(), pixbuf=self.overrides_store['modified-icon']) self.overrides_treeview.append_column(modified_col) sortable_cols.append((modified_col, 'text', 'modified')) self.set_sorts(self.overrides_store, sortable_cols) self.overrides_treeview.get_selection().connect( 'changed', self._on_selection) self.overrides_treeview.set_rules_hint(True) # Progress bar self.pb = None self.timer = 0 self.parent = parent self.main_window.set_transient_for(parent) def hide(self): self.main_window.hide() def show(self): self._load_data() def _load_data(self): self._show_progress_bar(_("Loading Repository Data"), _("Retrieving repository data from server."), self.parent) self.async_update.load_data(self._on_async_load_data_success, self._on_async_load_data_failure) def _on_async_load_data_success(self, current_overrides, current_repos): self.overrides_store.set_sort_column_id(0, ga_Gtk.SortType.ASCENDING) self._refresh(current_overrides, current_repos) self._clear_progress_bar() self.main_window.present() # By default sort by repo_id def _on_async_load_data_failure(self, e): self._clear_progress_bar() handle_gui_exception(e, _("Unable to load repository data."), self._get_dialog_widget()) def _refresh(self, current_overrides, current_repos, repo_id_to_select=None): # Current overrides from server overrides_per_repo = {} for override in current_overrides: repo_id = override.repo_id overrides_per_repo.setdefault(repo_id, {}) overrides_per_repo[repo_id][override.name] = override.value self.apply_button.set_sensitive(False) self.overrides_store.clear() self.other_overrides.clear() self.main_content_container.show_all() # Switch the dialog view depending on content availability if (current_repos): self.widget_switcher.set_active(1) self.details_vbox.show() else: ent_count = len(self.ent_dir.list_valid()) no_repos_message = self.ENTS_PROVIDE_NO_REPOS if ent_count == 0: no_repos_message = self.NO_ATTACHED_SUBS # Checks config for manage_repos. Output updated according to bz 1139174. if cfg.has_option('rhsm', 'manage_repos') and \ not int(cfg.get('rhsm', 'manage_repos')): no_repos_message = self.REPOS_DISABLED_BY_CFG self.no_repos_label.set_markup("<b>%s</b>" % no_repos_message) self.details_vbox.hide() self.widget_switcher.set_active(0) # Update the table model from our gathered override/repo data for repo in current_repos: overrides = overrides_per_repo.get(repo.id, None) self.overrides_store.add_map( self._build_table_row_data(repo, overrides)) first_row_iter = self.overrides_store.get_iter_first() if not first_row_iter: self.reset_button.set_sensitive(False) elif repo_id_to_select: self._select_by_repo_id(repo_id_to_select) else: self.overrides_treeview.get_selection().select_iter(first_row_iter) def _build_table_row_data(self, repo_data, repo_overrides): modified = not repo_overrides is None enabled = self._get_boolean( self._get_model_value(repo_data, repo_overrides, 'enabled')[0]) gpgcheck, gpgcheck_modified = self._get_model_value( repo_data, repo_overrides, 'gpgcheck') gpgcheck = self._get_boolean(gpgcheck) return { 'enabled': bool(int(enabled)), 'repo_id': repo_data.id, 'modified': modified, 'modified-icon': self._get_modified_icon(modified), 'name': repo_data['name'], 'baseurl': repo_data['baseurl'], 'gpgcheck': gpgcheck, 'gpgcheck_modified': gpgcheck_modified, 'repo_data': repo_data, 'override_data': repo_overrides } def _show_progress_bar(self, title, label, progress_parent=None): self.pb = progress.Progress(title, label, True) self.timer = ga_GObject.timeout_add(100, self.pb.pulse) self.pb.set_transient_for(progress_parent or self._get_dialog_widget()) def _clear_progress_bar(self): if self.pb: self.pb.hide() ga_GObject.source_remove(self.timer) self.timer = 0 self.pb = None def _get_modified_icon(self, modified): icon = None if modified: icon = self.modified_icon return icon def _get_selected_repo_id(self): selected = None override_selection = SelectionWrapper( self.overrides_treeview.get_selection(), self.overrides_store) if override_selection.is_valid(): selected = override_selection['repo_id'] return selected def _select_by_repo_id(self, repo_id): repo_data = (repo_id, self.overrides_store['repo_id']) self.overrides_store.foreach(self._select_repo_row, repo_data) def _select_repo_row(self, model, path, tree_iter, repo_data_tuple): """ Passed to model's foreach method to select the row if the repo_id matches. Returning True tells foreach to stop processing rows. """ repo_id, check_idx = repo_data_tuple row_repo_id = model.get_value(tree_iter, check_idx) if repo_id == row_repo_id: self.overrides_treeview.get_selection().select_iter(tree_iter) return True return False def _get_boolean(self, override_value): # An override value might come in as an int or a boolean string. # Try our best to convert it, and default to 0. try: val = int(override_value) except ValueError: val = 0 if override_value is not None and override_value.upper() == "TRUE": val = 1 return bool(val) def _get_model_value(self, repo, overrides, property_name): if not overrides or not property_name in overrides: return (repo[property_name], False) return (overrides[property_name], True) def _on_reset_repo(self, button): selection = SelectionWrapper(self.overrides_treeview.get_selection(), self.overrides_store) if not selection.is_valid(): return confirm = YesNoDialog( _("Are you sure you want to remove all overrides for <b>%s</b>?") % selection['repo_id'], self._get_dialog_widget(), _("Confirm Remove All Overrides")) confirm.connect("response", self._on_reset_repo_response) def _on_reset_repo_response(self, dialog, response): if not response: return selection = SelectionWrapper(self.overrides_treeview.get_selection(), self.overrides_store) if not selection.is_valid(): return repo_id = selection['repo_id'] self._show_progress_bar( _("Removing Repository Overrides"), _("Removing all overrides for repository <b>%s</b>") % repo_id) self.async_update.remove_all_overrides( [repo_id], self._on_async_delete_all_overrides_success, self._on_async_delete_all_overrides_failure) def _on_async_delete_all_overrides_success(self, current_overrides, current_repos): selection = SelectionWrapper(self.overrides_treeview.get_selection(), self.overrides_store) repo_id = selection['repo_id'] repo_data = None for repo in current_repos: if repo_id == repo.id: repo_data = repo break if repo_data: override_data = None for override in current_overrides: if repo_id == override.repo_id: override_data = override break row_data = self._build_table_row_data(repo_data, override_data) self.overrides_store.update_map(selection.tree_iter, row_data) # Update the UI based on the current selection as no selection change is # triggered, but data may enable/disable different widgets based on data # change. self._on_selection(self.overrides_treeview.get_selection()) self._clear_progress_bar() def _on_async_delete_all_overrides_failure(self, e): self._clear_progress_bar() handle_gui_exception(e, _("Unable to reset repository overrides."), self._get_dialog_widget()) def _on_selection(self, tree_selection): selection = SelectionWrapper(tree_selection, self.overrides_store) self.other_overrides.clear() reset_enabled = False if selection.is_valid(): overrides = selection['override_data'] reset_enabled = overrides is not None and len(overrides) > 0 self.name_text.set_text(selection['name']) self.baseurl_text.set_text(selection['baseurl']) for key, value in list((selection['override_data'] or {}).items()): if key not in ['gpgcheck', 'enabled']: self.other_overrides.add_override(key, value) self.reset_button.set_sensitive(reset_enabled) def _get_changed_overrides(self): override_mapping = {'to_add': [], 'to_remove': []} # Process each row in the model and build up a mapping of overrides. self.overrides_store.foreach(self._get_overrides_for_row, override_mapping) return override_mapping def _on_apply_request(self, button, event=None): override_mapping = self._get_changed_overrides() self._apply_override_changes(override_mapping, self._on_update_success) def _apply_override_changes(self, override_mapping, success_handler): self._show_progress_bar( _("Updating Repository Overrides"), _("Applying override changes to repositories.")) self.async_update.update_overrides(override_mapping['to_add'], override_mapping['to_remove'], success_handler, self._on_update_failure) def _on_update_success(self, current_overrides, current_repos): self._refresh(current_overrides, current_repos, self._get_selected_repo_id()) self._clear_progress_bar() def _on_update_failure(self, e): handle_gui_exception(e, _("Unable to update overrides."), self._get_dialog_widget()) self._clear_progress_bar() def _on_close(self, button, event=None): override_mapping = self._get_changed_overrides() if (len(override_mapping["to_add"]) == 0 and len(override_mapping["to_remove"]) == 0): self.close_dialog() return True # There are changes pending, check if the user would like to save changes. confirm = YesNoDialog(_("Repositories have changes. Save changes?"), self._get_dialog_widget(), _("Save Changes")) confirm.connect("response", self._on_apply_changes_on_close_response, override_mapping) def _on_apply_changes_on_close_response(self, dialog, response, override_mapping): if not response: self.close_dialog() return self._apply_override_changes(override_mapping, self._on_async_close_request) def _on_async_close_request(self, current_overrides, current_repos): self.close_dialog() def close_dialog(self): self._clear_progress_bar() self.hide() def _get_overrides_for_row(self, model, path, iter, override_mapping): self._update_override_mapping("gpgcheck", model, path, iter, override_mapping) self._update_override_mapping("enabled", model, path, iter, override_mapping) def _update_override_mapping(self, attribute, model, path, iter, override_mapping): ''' Process a single model row and determine if an override should be added/removed. ''' repo = model.get_value(iter, model['repo_data']) remote_overrides = model.get_value(iter, model['override_data']) repo_attribute_value = self._get_boolean(repo[attribute]) model_attribute_value = self._get_boolean( model.get_value(iter, model[attribute])) has_remote_override = remote_overrides and attribute in remote_overrides if not has_remote_override and model_attribute_value != repo_attribute_value: override_mapping["to_add"].append( Override(repo.id, attribute, str(int(model_attribute_value)))) elif has_remote_override and model_attribute_value == repo_attribute_value: override_mapping["to_remove"].append(Override(repo.id, attribute)) def _on_toggle_changed(self, override_model_iter, enabled, key): ''' Update the gui based on check box change. ''' repo = self.overrides_store.get_value( override_model_iter, self.overrides_store['repo_data']) overrides = self.overrides_store.get_value( override_model_iter, self.overrides_store['override_data']) current_gpg_check = self._get_boolean( self.overrides_store.get_value(override_model_iter, self.overrides_store['gpgcheck'])) repo_gpg_check = self._get_boolean(repo['gpgcheck']) current_enabled = self._get_boolean( self.overrides_store.get_value(override_model_iter, self.overrides_store['enabled'])) repo_enabled = self._get_boolean(repo['enabled']) has_extra = self._has_extra_overrides(overrides) mark_modified = has_extra or repo_gpg_check != current_gpg_check or repo_enabled != current_enabled enable_reset = overrides is not None and len(overrides) > 0 self.reset_button.set_sensitive(enable_reset) self.overrides_store.set_value(override_model_iter, self.overrides_store['modified'], mark_modified) self.overrides_store.set_value(override_model_iter, self.overrides_store['modified-icon'], self._get_modified_icon(mark_modified)) changed = self._get_changed_overrides() activate_apply_button = len(changed["to_add"]) != 0 or len( changed["to_remove"]) != 0 self.apply_button.set_sensitive(activate_apply_button) def _has_extra_overrides(self, override_data): for key, value in list((override_data or {}).items()): if key not in ['gpgcheck', 'enabled']: return True return False def _on_enable_repo_toggle(self, override_model_iter, enabled): return self._on_toggle_changed(override_model_iter, enabled, 'enabled') def _on_gpgcheck_toggle_changed(self, override_model_iter, enabled): return self._on_toggle_changed(override_model_iter, enabled, 'gpgcheck') def _get_dialog_widget(self): return self.main_window
class ContractSelectionWindow(object): def __init__(self, selected_callback, cancel_callback): self._selected_callback = selected_callback self._cancel_callback = cancel_callback self.total_contracts = 0 self.contract_selection_xml = gtk.glade.XML(CONTRACT_SELECTION_GLADE) self.contract_selection_win = self.contract_selection_xml.get_widget( "contract_selection_window") self.subscribe_button = self.contract_selection_xml.get_widget( 'subscribe_button') self.edit_quantity_label = self.contract_selection_xml.get_widget( 'edit_quantity_label') self.contract_selection_treeview = \ self.contract_selection_xml.get_widget( "contract_selection_treeview") self.contract_selection_treeview.get_selection().connect( "changed", self._on_contract_selection) self.subscription_name_label = self.contract_selection_xml.get_widget( "subscription_name_label") self.total_contracts_label = self.contract_selection_xml.get_widget( "total_contracts_label") self.contract_selection_xml.signal_autoconnect({ "on_cancel_button_clicked": self._cancel_button_clicked, "on_subscribe_button_clicked": self._subscribe_button_clicked, }) self.model = MappedListStore(self.get_type_map()) self.contract_selection_treeview.set_model(self.model) def get_type_map(self): return { 'contract_number': str, 'consumed_fraction': str, 'start_date': gobject.TYPE_PYOBJECT, 'end_date': gobject.TYPE_PYOBJECT, 'default_quantity': int, 'product_name': str, 'pool': gobject.TYPE_PYOBJECT, 'is_virt_only': bool, 'multi_entitlement': bool, 'quantity_available': int, 'quantity_increment': int, } def show(self): self.populate_treeview() self.contract_selection_win.show_all() def destroy(self): self.contract_selection_win.destroy() def populate_treeview(self): renderer = gtk.CellRendererText() column = gtk.TreeViewColumn(_("Contract"), renderer, text=self.model['contract_number']) column.set_expand(True) column.set_sort_column_id(self.model['contract_number']) self.model.set_sort_func(self.model['contract_number'], self._sort_text, None) self.contract_selection_treeview.append_column(column) column = widgets.MachineTypeColumn(self.model['is_virt_only']) column.set_sort_column_id(self.model['is_virt_only']) self.model.set_sort_func(self.model['is_virt_only'], self._sort_machine_type, column) self.contract_selection_treeview.append_column(column) renderer = gtk.CellRendererText() renderer.set_property("xalign", 0.5) column = gtk.TreeViewColumn(_("Used / Total"), renderer, text=self.model['consumed_fraction']) self.contract_selection_treeview.append_column(column) renderer = widgets.CellRendererDate() column = gtk.TreeViewColumn(_("Start Date"), renderer, date=self.model['start_date']) column.set_sort_column_id(self.model['start_date']) self.model.set_sort_func(self.model['start_date'], self._sort_date, None) self.contract_selection_treeview.append_column(column) renderer = widgets.CellRendererDate() column = gtk.TreeViewColumn(_("End Date"), renderer, date=self.model['end_date']) column.set_sort_column_id(self.model['end_date']) self.model.set_sort_func(self.model['end_date'], self._sort_date, None) self.contract_selection_treeview.append_column(column) column = widgets.QuantitySelectionColumn( _("Quantity"), self.model, self.model['default_quantity'], self.model['multi_entitlement'], self.model['quantity_available'], self.model['quantity_increment']) self.contract_selection_treeview.append_column(column) self.edit_quantity_label.set_label(column.get_column_legend_text()) def add_pool(self, pool, default_quantity_value): self.total_contracts += 1 self.total_contracts_label.set_text(str(self.total_contracts)) self.subscription_name_label.set_text(pool['productName']) # Use unlimited for -1 quanities quantity = pool['quantity'] if quantity < 0: quantity = _('Unlimited') quantity_available = -1 else: quantity_available = int(pool['quantity']) - int(pool['consumed']) # cap the default selected quantity at the max available # for that pool. See #855257. Watch out for quantity_available # being -1 (unlimited). if default_quantity_value > quantity_available and quantity_available >= 0: default_quantity_value = quantity_available quantity_increment = 1 if 'calculatedAttributes' in pool: calculated_attrs = pool['calculatedAttributes'] if 'quantity_increment' in calculated_attrs: quantity_increment = int( calculated_attrs['quantity_increment']) self.model.add_map({ 'contract_number': pool['contractNumber'], 'consumed_fraction': "%s / %s" % (pool['consumed'], quantity), 'start_date': isodate.parse_date(pool['startDate']), 'end_date': isodate.parse_date(pool['endDate']), 'default_quantity': default_quantity_value, 'product_name': pool['productName'], 'pool': pool, 'is_virt_only': PoolWrapper(pool).is_virt_only(), 'multi_entitlement': allows_multi_entitlement(pool), 'quantity_available': quantity_available, 'quantity_increment': quantity_increment, }) def set_parent_window(self, window): self.contract_selection_win.set_transient_for(window) def _cancel_button_clicked(self, button): self._cancel_callback() def _subscribe_button_clicked(self, button): selection = self.contract_selection_treeview.get_selection() model, iter = selection.get_selected() if iter is not None: pool, quantity = model.get(iter, model['pool'], model['default_quantity']) self._selected_callback(pool, quantity) def _on_contract_selection(self, widget): model, tree_iter = widget.get_selected() enabled = True if not tree_iter: enabled = False self.subscribe_button.set_sensitive(enabled) def _sort_text(self, model, row1, row2, data): sort_column, sort_type = model.get_sort_column_id() str1 = model.get_value(row1, sort_column) str2 = model.get_value(row2, sort_column) return cmp(str1, str2) def _sort_machine_type(self, model, row1, row2, col): # Machine type is actually a boolean denoting whether the type is # virtual or not. We do not want to sort on the boolean value. # Instead we want to sort on the text value that is going to get # displayed for the boolean value. sort_column, sort_type = model.get_sort_column_id() results = [] for row in [row1, row2]: _bool = model.get_value(row, sort_column) if _bool is None: text = col._get_none_text() elif bool(_bool): text = col._get_true_text() else: text = col._get_false_text() results.append(text) return cmp(results[0], results[1]) def _sort_date(self, model, row1, row2, data): """ Used for sorting dates that could be None. """ sort_column, sort_type = model.get_sort_column_id() date1 = model.get_value(row1, sort_column) \ or datetime.date(datetime.MINYEAR, 1, 1) date2 = model.get_value(row2, sort_column) \ or datetime.date(datetime.MINYEAR, 1, 1) epoch1 = time.mktime(date1.timetuple()) epoch2 = time.mktime(date2.timetuple()) return cmp(epoch1, epoch2)
def __init__(self, backend, parent): super(RepositoriesDialog, self).__init__() # Label wrapping on resize does not work very well on GTK2+ # so we resize on the fly. if gtk_compat.GTK_COMPAT_VERSION == "2": # Set the title to wrap and connect to size-allocate to # properly resize the label so that it takes up the most # space it can. self.description_text.set_line_wrap(True) self.description_text.connect('size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1)) self.name_text.set_line_wrap(True) self.name_text.connect('size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1)) self.baseurl_text.set_line_wrap(True) self.baseurl_text.connect('size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1)) # Set up dynamic elements self.overrides_treeview = ga_Gtk.TreeView() # Add at-spi because we no longer create this widget from glade self.overrides_treeview.get_accessible().set_name("Repository View") self.no_repos_label, self.no_repos_label_viewport = widgets.get_scrollable_label() self.widget_switcher = widgets.WidgetSwitcher(self.scrolledwindow, self.no_repos_label_viewport, self.overrides_treeview) # We require the backend here so that we can always use its version # of Overrides which will guarantee that the CP UEPConnection is up # to date. # FIXME: We really shouldn't have to worry about our connection info # changing out from under us. self.backend = backend self.async_update = AsyncRepoOverridesUpdate(self.backend.overrides) self.identity = require(IDENTITY) self.ent_dir = require(ENT_DIR) self.connect_signals({"on_dialog_delete_event": self._on_close, "on_close_button_clicked": self._on_close, "on_apply_button_clicked": self._on_apply_request, "on_reset_button_clicked": self._on_reset_repo}) self.overrides_store = MappedListStore({ "repo_id": str, "enabled": bool, "modified": bool, "modified-icon": ga_GdkPixbuf.Pixbuf, "name": str, "baseurl": str, "gpgcheck": bool, "gpgcheck_modified": bool, "repo_data": object, "override_data": object }) self.other_overrides = OverridesTable(self.other_overrides_view) # FIXME: think this needs get_style_context() and possible a # Gtk.StyleProvider for gtk3 # Change the background color of the no_repos_label_container to the same color # as the label's base color. The event container allows us to change the color. #label_base_color = self.no_repos_label.get_style_context().base[Gtk.StateType.NORMAL] #self.no_repos_label_viewport.modify_bg(Gtk.StateType.NORMAL, label_base_color) self.overrides_treeview.set_model(self.overrides_store) self.modified_icon = self.overrides_treeview.render_icon(ga_Gtk.STOCK_APPLY, ga_Gtk.IconSize.MENU) sortable_cols = [] enabled_col = CheckBoxColumn(_("Enabled"), self.overrides_store, 'enabled', self._on_enable_repo_toggle) self.overrides_treeview.append_column(enabled_col) gpgcheck_col = CheckBoxColumn(_("Gpgcheck"), self.overrides_store, 'gpgcheck', self._on_gpgcheck_toggle_changed) self.overrides_treeview.append_column(gpgcheck_col) repo_id_col = TextTreeViewColumn(self.overrides_store, _("Repository ID"), 'repo_id', expand=True) self.overrides_treeview.append_column(repo_id_col) sortable_cols.append((repo_id_col, 'text', 'repo_id')) modified_col = ga_Gtk.TreeViewColumn(_("Modified"), ga_Gtk.CellRendererPixbuf(), pixbuf=self.overrides_store['modified-icon']) self.overrides_treeview.append_column(modified_col) sortable_cols.append((modified_col, 'text', 'modified')) self.set_sorts(self.overrides_store, sortable_cols) self.overrides_treeview.get_selection().connect('changed', self._on_selection) self.overrides_treeview.set_rules_hint(True) # Progress bar self.pb = None self.timer = 0 self.parent = parent self.main_window.set_transient_for(parent)
class RepositoriesDialog(widgets.SubmanBaseWidget, HasSortableWidget): """ GTK dialog for managing repositories and their overrides. """ widget_names = ['main_window', 'reset_button', 'close_button', 'apply_button', 'name_text', 'baseurl_text', 'scrolledwindow', 'other_overrides_view', 'details_vbox', 'main_content_container', 'description_text'] gui_file = "repositories" ENTS_PROVIDE_NO_REPOS = _("Attached subscriptions do not provide any repositories.") NO_ATTACHED_SUBS = _("No repositories are available without an attached subscription.") REPOS_DISABLED_BY_CFG = _("Repositories disabled by configuration.") def __init__(self, backend, parent): super(RepositoriesDialog, self).__init__() # Label wrapping on resize does not work very well on GTK2+ # so we resize on the fly. if gtk_compat.GTK_COMPAT_VERSION == "2": # Set the title to wrap and connect to size-allocate to # properly resize the label so that it takes up the most # space it can. self.description_text.set_line_wrap(True) self.description_text.connect('size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1)) self.name_text.set_line_wrap(True) self.name_text.connect('size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1)) self.baseurl_text.set_line_wrap(True) self.baseurl_text.connect('size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1)) # Set up dynamic elements self.overrides_treeview = ga_Gtk.TreeView() # Add at-spi because we no longer create this widget from glade self.overrides_treeview.get_accessible().set_name("Repository View") self.no_repos_label, self.no_repos_label_viewport = widgets.get_scrollable_label() self.widget_switcher = widgets.WidgetSwitcher(self.scrolledwindow, self.no_repos_label_viewport, self.overrides_treeview) # We require the backend here so that we can always use its version # of Overrides which will guarantee that the CP UEPConnection is up # to date. # FIXME: We really shouldn't have to worry about our connection info # changing out from under us. self.backend = backend self.async_update = AsyncRepoOverridesUpdate(self.backend.overrides) self.identity = require(IDENTITY) self.ent_dir = require(ENT_DIR) self.connect_signals({"on_dialog_delete_event": self._on_close, "on_close_button_clicked": self._on_close, "on_apply_button_clicked": self._on_apply_request, "on_reset_button_clicked": self._on_reset_repo}) self.overrides_store = MappedListStore({ "repo_id": str, "enabled": bool, "modified": bool, "modified-icon": ga_GdkPixbuf.Pixbuf, "name": str, "baseurl": str, "gpgcheck": bool, "gpgcheck_modified": bool, "repo_data": object, "override_data": object }) self.other_overrides = OverridesTable(self.other_overrides_view) # FIXME: think this needs get_style_context() and possible a # Gtk.StyleProvider for gtk3 # Change the background color of the no_repos_label_container to the same color # as the label's base color. The event container allows us to change the color. #label_base_color = self.no_repos_label.get_style_context().base[Gtk.StateType.NORMAL] #self.no_repos_label_viewport.modify_bg(Gtk.StateType.NORMAL, label_base_color) self.overrides_treeview.set_model(self.overrides_store) self.modified_icon = self.overrides_treeview.render_icon(ga_Gtk.STOCK_APPLY, ga_Gtk.IconSize.MENU) sortable_cols = [] enabled_col = CheckBoxColumn(_("Enabled"), self.overrides_store, 'enabled', self._on_enable_repo_toggle) self.overrides_treeview.append_column(enabled_col) gpgcheck_col = CheckBoxColumn(_("Gpgcheck"), self.overrides_store, 'gpgcheck', self._on_gpgcheck_toggle_changed) self.overrides_treeview.append_column(gpgcheck_col) repo_id_col = TextTreeViewColumn(self.overrides_store, _("Repository ID"), 'repo_id', expand=True) self.overrides_treeview.append_column(repo_id_col) sortable_cols.append((repo_id_col, 'text', 'repo_id')) modified_col = ga_Gtk.TreeViewColumn(_("Modified"), ga_Gtk.CellRendererPixbuf(), pixbuf=self.overrides_store['modified-icon']) self.overrides_treeview.append_column(modified_col) sortable_cols.append((modified_col, 'text', 'modified')) self.set_sorts(self.overrides_store, sortable_cols) self.overrides_treeview.get_selection().connect('changed', self._on_selection) self.overrides_treeview.set_rules_hint(True) # Progress bar self.pb = None self.timer = 0 self.parent = parent self.main_window.set_transient_for(parent) def hide(self): self.main_window.hide() def show(self): self._load_data() def _load_data(self): self._show_progress_bar(_("Loading Repository Data"), _("Retrieving repository data from server."), self.parent) self.async_update.load_data(self._on_async_load_data_success, self._on_async_load_data_failure) def _on_async_load_data_success(self, current_overrides, current_repos): self.overrides_store.set_sort_column_id(0, ga_Gtk.SortType.ASCENDING) self._refresh(current_overrides, current_repos) self._clear_progress_bar() self.main_window.present() # By default sort by repo_id def _on_async_load_data_failure(self, e): self._clear_progress_bar() handle_gui_exception(e, _("Unable to load repository data."), self._get_dialog_widget()) def _refresh(self, current_overrides, current_repos, repo_id_to_select=None): # Current overrides from server overrides_per_repo = {} for override in current_overrides: repo_id = override.repo_id overrides_per_repo.setdefault(repo_id, {}) overrides_per_repo[repo_id][override.name] = override.value self.apply_button.set_sensitive(False) self.overrides_store.clear() self.other_overrides.clear() self.main_content_container.show_all() # Switch the dialog view depending on content availability if (current_repos): self.widget_switcher.set_active(1) self.details_vbox.show() else: ent_count = len(self.ent_dir.list_valid()) no_repos_message = self.ENTS_PROVIDE_NO_REPOS if ent_count == 0: no_repos_message = self.NO_ATTACHED_SUBS # Checks config for manage_repos. Output updated according to bz 1139174. if cfg.has_option('rhsm', 'manage_repos') and \ not int(cfg.get('rhsm', 'manage_repos')): no_repos_message = self.REPOS_DISABLED_BY_CFG self.no_repos_label.set_markup("<b>%s</b>" % no_repos_message) self.details_vbox.hide() self.widget_switcher.set_active(0) # Update the table model from our gathered override/repo data for repo in current_repos: overrides = overrides_per_repo.get(repo.id, None) self.overrides_store.add_map(self._build_table_row_data(repo, overrides)) first_row_iter = self.overrides_store.get_iter_first() if not first_row_iter: self.reset_button.set_sensitive(False) elif repo_id_to_select: self._select_by_repo_id(repo_id_to_select) else: self.overrides_treeview.get_selection().select_iter(first_row_iter) def _build_table_row_data(self, repo_data, repo_overrides): modified = not repo_overrides is None enabled = self._get_boolean(self._get_model_value(repo_data, repo_overrides, 'enabled')[0]) gpgcheck, gpgcheck_modified = self._get_model_value(repo_data, repo_overrides, 'gpgcheck') gpgcheck = self._get_boolean(gpgcheck) return { 'enabled': bool(int(enabled)), 'repo_id': repo_data.id, 'modified': modified, 'modified-icon': self._get_modified_icon(modified), 'name': repo_data['name'], 'baseurl': repo_data['baseurl'], 'gpgcheck': gpgcheck, 'gpgcheck_modified': gpgcheck_modified, 'repo_data': repo_data, 'override_data': repo_overrides } def _show_progress_bar(self, title, label, progress_parent=None): self.pb = progress.Progress(title, label, True) self.timer = ga_GObject.timeout_add(100, self.pb.pulse) self.pb.set_transient_for(progress_parent or self._get_dialog_widget()) def _clear_progress_bar(self): if self.pb: self.pb.hide() ga_GObject.source_remove(self.timer) self.timer = 0 self.pb = None def _get_modified_icon(self, modified): icon = None if modified: icon = self.modified_icon return icon def _get_selected_repo_id(self): selected = None override_selection = SelectionWrapper(self.overrides_treeview.get_selection(), self.overrides_store) if override_selection.is_valid(): selected = override_selection['repo_id'] return selected def _select_by_repo_id(self, repo_id): repo_data = (repo_id, self.overrides_store['repo_id']) self.overrides_store.foreach(self._select_repo_row, repo_data) def _select_repo_row(self, model, path, tree_iter, repo_data_tuple): """ Passed to model's foreach method to select the row if the repo_id matches. Returning True tells foreach to stop processing rows. """ repo_id, check_idx = repo_data_tuple row_repo_id = model.get_value(tree_iter, check_idx) if repo_id == row_repo_id: self.overrides_treeview.get_selection().select_iter(tree_iter) return True return False def _get_boolean(self, override_value): # An override value might come in as an int or a boolean string. # Try our best to convert it, and default to 0. try: val = int(override_value) except ValueError: val = 0 if override_value is not None and override_value.upper() == "TRUE": val = 1 return bool(val) def _get_model_value(self, repo, overrides, property_name): if not overrides or not property_name in overrides: return (repo[property_name], False) return (overrides[property_name], True) def _on_reset_repo(self, button): selection = SelectionWrapper(self.overrides_treeview.get_selection(), self.overrides_store) if not selection.is_valid(): return confirm = YesNoDialog(_("Are you sure you want to remove all overrides for <b>%s</b>?") % selection['repo_id'], self._get_dialog_widget(), _("Confirm Remove All Overrides")) confirm.connect("response", self._on_reset_repo_response) def _on_reset_repo_response(self, dialog, response): if not response: return selection = SelectionWrapper(self.overrides_treeview.get_selection(), self.overrides_store) if not selection.is_valid(): return repo_id = selection['repo_id'] self._show_progress_bar(_("Removing Repository Overrides"), _("Removing all overrides for repository <b>%s</b>") % repo_id) self.async_update.remove_all_overrides([repo_id], self._on_async_delete_all_overrides_success, self._on_async_delete_all_overrides_failure) def _on_async_delete_all_overrides_success(self, current_overrides, current_repos): selection = SelectionWrapper(self.overrides_treeview.get_selection(), self.overrides_store) repo_id = selection['repo_id'] repo_data = None for repo in current_repos: if repo_id == repo.id: repo_data = repo break if repo_data: override_data = None for override in current_overrides: if repo_id == override.repo_id: override_data = override break row_data = self._build_table_row_data(repo_data, override_data) self.overrides_store.update_map(selection.tree_iter, row_data) # Update the UI based on the current selection as no selection change is # triggered, but data may enable/disable different widgets based on data # change. self._on_selection(self.overrides_treeview.get_selection()) self._clear_progress_bar() def _on_async_delete_all_overrides_failure(self, e): self._clear_progress_bar() handle_gui_exception(e, _("Unable to reset repository overrides."), self._get_dialog_widget()) def _on_selection(self, tree_selection): selection = SelectionWrapper(tree_selection, self.overrides_store) self.other_overrides.clear() reset_enabled = False if selection.is_valid(): overrides = selection['override_data'] reset_enabled = overrides is not None and len(overrides) > 0 self.name_text.set_text(selection['name']) self.baseurl_text.set_text(selection['baseurl']) for key, value in (selection['override_data'] or {}).items(): if key not in ['gpgcheck', 'enabled']: self.other_overrides.add_override(key, value) self.reset_button.set_sensitive(reset_enabled) def _get_changed_overrides(self): override_mapping = { 'to_add': [], 'to_remove': [] } # Process each row in the model and build up a mapping of overrides. self.overrides_store.foreach(self._get_overrides_for_row, override_mapping) return override_mapping def _on_apply_request(self, button, event=None): override_mapping = self._get_changed_overrides() self._apply_override_changes(override_mapping, self._on_update_success) def _apply_override_changes(self, override_mapping, success_handler): self._show_progress_bar(_("Updating Repository Overrides"), _("Applying override changes to repositories.")) self.async_update.update_overrides(override_mapping['to_add'], override_mapping['to_remove'], success_handler, self._on_update_failure) def _on_update_success(self, current_overrides, current_repos): self._refresh(current_overrides, current_repos, self._get_selected_repo_id()) self._clear_progress_bar() def _on_update_failure(self, e): handle_gui_exception(e, _("Unable to update overrides."), self._get_dialog_widget()) self._clear_progress_bar() def _on_close(self, button, event=None): override_mapping = self._get_changed_overrides() if (len(override_mapping["to_add"]) == 0 and len(override_mapping["to_remove"]) == 0): self.close_dialog() return True # There are changes pending, check if the user would like to save changes. confirm = YesNoDialog(_("Repositories have changes. Save changes?"), self._get_dialog_widget(), _("Save Changes")) confirm.connect("response", self._on_apply_changes_on_close_response, override_mapping) def _on_apply_changes_on_close_response(self, dialog, response, override_mapping): if not response: self.close_dialog() return self._apply_override_changes(override_mapping, self._on_async_close_request) def _on_async_close_request(self, current_overrides, current_repos): self.close_dialog() def close_dialog(self): self._clear_progress_bar() self.hide() def _get_overrides_for_row(self, model, path, iter, override_mapping): self._update_override_mapping("gpgcheck", model, path, iter, override_mapping) self._update_override_mapping("enabled", model, path, iter, override_mapping) def _update_override_mapping(self, attribute, model, path, iter, override_mapping): ''' Process a single model row and determine if an override should be added/removed. ''' repo = model.get_value(iter, model['repo_data']) remote_overrides = model.get_value(iter, model['override_data']) repo_attribute_value = self._get_boolean(repo[attribute]) model_attribute_value = self._get_boolean(model.get_value(iter, model[attribute])) has_remote_override = remote_overrides and attribute in remote_overrides if not has_remote_override and model_attribute_value != repo_attribute_value: override_mapping["to_add"].append(Override(repo.id, attribute, str(int(model_attribute_value)))) elif has_remote_override and model_attribute_value == repo_attribute_value: override_mapping["to_remove"].append(Override(repo.id, attribute)) def _on_toggle_changed(self, override_model_iter, enabled, key): ''' Update the gui based on check box change. ''' repo = self.overrides_store.get_value(override_model_iter, self.overrides_store['repo_data']) overrides = self.overrides_store.get_value(override_model_iter, self.overrides_store['override_data']) current_gpg_check = self._get_boolean(self.overrides_store.get_value(override_model_iter, self.overrides_store['gpgcheck'])) repo_gpg_check = self._get_boolean(repo['gpgcheck']) current_enabled = self._get_boolean(self.overrides_store.get_value(override_model_iter, self.overrides_store['enabled'])) repo_enabled = self._get_boolean(repo['enabled']) has_extra = self._has_extra_overrides(overrides) mark_modified = has_extra or repo_gpg_check != current_gpg_check or repo_enabled != current_enabled enable_reset = overrides is not None and len(overrides) > 0 self.reset_button.set_sensitive(enable_reset) self.overrides_store.set_value(override_model_iter, self.overrides_store['modified'], mark_modified) self.overrides_store.set_value(override_model_iter, self.overrides_store['modified-icon'], self._get_modified_icon(mark_modified)) changed = self._get_changed_overrides() activate_apply_button = len(changed["to_add"]) != 0 or len(changed["to_remove"]) != 0 self.apply_button.set_sensitive(activate_apply_button) def _has_extra_overrides(self, override_data): for key, value in (override_data or {}).items(): if key not in ['gpgcheck', 'enabled']: return True return False def _on_enable_repo_toggle(self, override_model_iter, enabled): return self._on_toggle_changed(override_model_iter, enabled, 'enabled') def _on_gpgcheck_toggle_changed(self, override_model_iter, enabled): return self._on_toggle_changed(override_model_iter, enabled, 'gpgcheck') def _get_dialog_widget(self): return self.main_window
def __init__(self, backend, parent): super(RepositoriesDialog, self).__init__('repositories.glade') # Set up dynamic elements self.overrides_treeview = gtk.TreeView() self.no_repos_label, self.no_repos_label_viewport = widgets.get_scrollable_label() self.widget_switcher = widgets.WidgetSwitcher(self.scrolledwindow, self.no_repos_label_viewport, self.overrides_treeview) # We require the backend here so that we can always use its version # of Overrides which will guarantee that the CP UEPConnection is up # to date. # FIXME: We really shouldn't have to worry about our connection info # changing out from under us. self.backend = backend self.identity = require(IDENTITY) self.ent_dir = require(ENT_DIR) self.glade.signal_autoconnect({ "on_dialog_delete_event": self._on_close, "on_close_button_clicked": self._on_close, "on_reset_button_clicked": self._on_reset_repo, "on_gpgcheck_edit_button_clicked": self._on_gpgcheck_edit_button_clicked, "on_gpgcheck_remove_button_clicked": self._on_gpgcheck_remove_button_clicked, "on_gpgcheck_combo_box_changed": self._on_gpgcheck_combo_box_changed, }) self.overrides_store = MappedListStore({ "repo_id": str, "enabled": bool, "modified": bool, "modified-icon": gtk.gdk.Pixbuf, "name": str, "baseurl": str, "gpgcheck": bool, "gpgcheck_modified": bool, "repo_data": object, "override_data": object }) # Change the background color of the no_repos_label_container to the same color # as the label's base color. The event container allows us to change the color. label_base_color = self.no_repos_label.style.base[gtk.STATE_NORMAL] self.no_repos_label_viewport.modify_bg(gtk.STATE_NORMAL, label_base_color) # Gnome will hide all button icons by default (gnome setting), # so force the icons to show in this case as there is no button # text, just the icon. gpgcheck_edit_image = gtk.Image() gpgcheck_edit_image.set_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_BUTTON) self.gpgcheck_edit_button.set_image(gpgcheck_edit_image) self.gpgcheck_edit_button.get_image().show() gpgcheck_reset_image = gtk.Image() gpgcheck_reset_image.set_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_BUTTON) self.gpgcheck_remove_button.set_image(gpgcheck_reset_image) self.gpgcheck_remove_button.get_image().show() self.overrides_treeview.set_model(self.overrides_store) self.modified_icon = self.overrides_treeview.render_icon(gtk.STOCK_APPLY, gtk.ICON_SIZE_MENU) sortable_cols = [] enabled_col = CheckBoxColumn(_("Enabled"), self.overrides_store, 'enabled', self._on_enable_repo_toggle) self.overrides_treeview.append_column(enabled_col) repo_id_col = TextTreeViewColumn(self.overrides_store, _("Repository ID"), 'repo_id', expand=True) self.overrides_treeview.append_column(repo_id_col) sortable_cols.append((repo_id_col, 'text', 'repo_id')) modified_col = gtk.TreeViewColumn(_("Modified"), gtk.CellRendererPixbuf(), pixbuf=self.overrides_store['modified-icon']) self.overrides_treeview.append_column(modified_col) sortable_cols.append((modified_col, 'text', 'modified')) self.set_sorts(self.overrides_store, sortable_cols) self.overrides_treeview.get_selection().connect('changed', self._on_selection) self.overrides_treeview.set_rules_hint(True) self.gpgcheck_cb_model = gtk.ListStore(str, bool) self.gpgcheck_cb_model.append((_("Enabled"), True)) self.gpgcheck_cb_model.append((_("Disabled"), False)) gpgcheck_cell_renderer = gtk.CellRendererText() self.gpgcheck_combo_box.pack_start(gpgcheck_cell_renderer, True) self.gpgcheck_combo_box.add_attribute(gpgcheck_cell_renderer, "text", 0) self.gpgcheck_combo_box.set_model(self.gpgcheck_cb_model) self.gpgcheck_combo_box.set_active(0) self.main_window.set_transient_for(parent)