class AdditionListSlave(SearchSlave): """A slave that offers a simple list and its management. This slave also has the option to display a small message right next to the buttons """ domain = 'stoq' toplevel_name = gladefile = 'AdditionListSlave' widgets = ('add_button', 'delete_button', 'klist', 'list_vbox', 'edit_button') gsignal('on-edit-item', object) gsignal('on-add-item', object) gsignal('before-delete-items', object) gsignal('after-delete-items') def __init__(self, store, columns=None, editor_class=None, klist_objects=None, visual_mode=False, restore_name=None, tree=False): """ Creates a new AdditionListSlave object :param store: a store :param columns: column definitions :type columns: sequence of :class:`kiwi.ui.objectlist.Columns` :param editor_class: the window that is going to be open when user clicks on add_button or edit_button. :type: editor_class: a :class:`stoqlib.gui.editors.BaseEditor` subclass :param klist_objects: initial objects to insert into the list :param visual_mode: if we are working on visual mode, that means, not possible to edit the model on this object type visual_mode: bool :param restore_name: the name used to save and restore the columns on a cache system (e.g. pickle) :type restore_name: basestring :param tree: Indication of which kind of list we are adding. If `True` ObjectTree otherwise ObjectList will be added """ columns = columns or self.get_columns() SearchSlave.__init__(self, columns=columns, restore_name=restore_name, store=store) self.tree = tree self.klist = ObjectTree() if tree else ObjectList() self.list_vbox.add(self.klist) self.list_vbox.show_all() if not self.columns: raise StoqlibError("columns must be specified") self.visual_mode = visual_mode self.store = store self.set_editor(editor_class) self._can_edit = True self._callback_id = None if self.visual_mode: self.hide_add_button() self.hide_edit_button() self.hide_del_button() items = klist_objects or self.get_items() self._setup_klist(items) self._update_sensitivity() def _setup_klist(self, klist_objects): self.klist.set_columns(self.columns) self.klist.set_selection_mode(gtk.SELECTION_MULTIPLE) if self.tree: (self.klist.append(obj.parent_item, obj) for obj in klist_objects) else: self.klist.add_list(klist_objects) if self.visual_mode: self.klist.set_sensitive(False) def _update_sensitivity(self, *args): if self.visual_mode: return can_delete = _can_edit = True objs = self.get_selection() if not objs: _can_edit = can_delete = False elif len(objs) > 1: _can_edit = False self.add_button.set_sensitive(True) self.edit_button.set_sensitive(_can_edit) self.delete_button.set_sensitive(can_delete) def _edit_model(self, model=None, parent=None): edit_mode = model result = self.run_editor(model) if not result: return if edit_mode: self.emit('on-edit-item', result) self.klist.update(result) else: if self.tree: self.klist.append(parent, result) else: self.klist.append(result) # Emit the signal after we added the item to the list to be able to # check the length of the list in our validation callbacks. self.emit('on-add-item', result) # As we have a selection extended mode for kiwi list, we # need to unselect everything before select the new instance. self.klist.unselect_all() self.klist.select(result) self._update_sensitivity() def _edit(self): if not self._can_edit: return objs = self.get_selection() qty = len(objs) if qty != 1: raise SelectionError( ("Please select only one item before choosing Edit." "\nThere are currently %d items selected") % qty) self._edit_model(objs[0]) def _clear(self): objs = self.get_selection() qty = len(objs) if qty < 1: raise SelectionError('There are no objects selected') msg = stoqlib_ngettext(_('Delete this item?'), _('Delete these %d items?') % qty, qty) delete_label = stoqlib_ngettext(_("Delete item"), _("Delete items"), qty) keep_label = stoqlib_ngettext(_("Keep it"), _("Keep them"), qty) if not yesno(msg, gtk.RESPONSE_NO, delete_label, keep_label): return self.emit('before-delete-items', objs) if qty == len(self.klist): self.klist.clear() else: for obj in objs: self.klist.remove(obj) self.klist.unselect_all() self._update_sensitivity() self.emit('after-delete-items') # # Hooks # def get_items(self): return [] def get_columns(self): raise NotImplementedError("get_columns must be implemented in " "subclasses") def run_editor(self, model): """This can be overriden to provide a custom run_dialog line, or a conversion function for the model """ if self._editor_class is None: raise TypeError( "%s cannot create or edit items without the editor_class " "argument set" % (self.__class__.__name__)) self.store.savepoint('before_run_editor_addition') retval = run_dialog(self._editor_class, None, store=self.store, model=model) if not retval: self.store.rollback_to_savepoint('before_run_editor_addition') return retval def delete_model(self, model): """Deletes a model, can be overridden in subclass :param model: model to delete """ model.__class__.delete(model.id, store=self.store) # # Public API # def add_extra_button(self, label=None, stock=None): """Add an extra button on the this slave The extra button will be appended at the end of the button box, the one containing the add/edit/delete buttons :param label: label of the button, can be ``None`` if stock is passed :param stock: stock label of the button, can be ``None`` if label is passed :param returns: the button added :rtype: gtk.Button """ if label is None and stock is None: raise TypeError("You need to provide a label or a stock argument") button = gtk.Button(label=label, stock=stock) button.set_property('can_focus', True) self.button_box.pack_end(button, False, False) button.show() return button def set_message(self, message, details_callback=None): """Display a simple message on a label, next to the add, edit, delete buttons :param message: a message with properly escaped markup """ self.message_hbox.set_visible(True) self.message_details_button.set_visible(bool(details_callback)) if details_callback: if self._callback_id: self.message_details_button.disconnect(self._callback_id) self._callback_id = self.message_details_button.connect( 'clicked', details_callback) self.message_label.set_markup(message) def clear_message(self): self.message_hbox.set_visible(False) def get_selection(self): # XXX: add get_selected_rows and raise exceptions if not in the # right mode if self.klist.get_selection_mode() == gtk.SELECTION_MULTIPLE: return self.klist.get_selected_rows() selection = self.klist.get_selected() if not selection: return [] return [selection] def hide_add_button(self): self.add_button.hide() def hide_edit_button(self): self._can_edit = False self.edit_button.hide() def hide_del_button(self): self.delete_button.hide() def set_editor(self, editor_class): if editor_class and not issubclass(editor_class, (BaseEditor, BaseWizard)): raise TypeError("editor_class must be a BaseEditor subclass") self._editor_class = editor_class # # Signal handlers # def on_klist__row_activated(self, *args): self._edit() def on_klist__selection_changed(self, *args): self._update_sensitivity() def on_add_button__clicked(self, button): self._edit_model() def on_edit_button__clicked(self, button): self._edit() def on_delete_button__clicked(self, button): self._clear()
class DetailsTab(gtk.VBox): details_dialog_class = None def __init__(self, model, parent): super(DetailsTab, self).__init__() self.model = model self._parent = parent self.set_spacing(6) self.set_border_width(6) self.klist = ObjectTree(self.get_columns()) self.populate() self.pack_start(self.klist) self.klist.show() if len(self.klist) and self.get_details_dialog_class(): self.button_box = gtk.HButtonBox() self.button_box.set_layout(gtk.BUTTONBOX_START) details_button = gtk.Button(self.details_lbl) self.button_box.pack_start(details_button) details_button.set_sensitive(bool(self.klist.get_selected())) details_button.show() self.pack_end(self.button_box, False, False) self.button_box.show() self.button_box.details_button = details_button details_button.connect('clicked', self._on_details_button__clicked) self.klist.connect('row-activated', self._on_klist__row_activated) self.klist.connect('selection-changed', self._on_klist__selection_changed) self.setup_widgets() def refresh(self): """Refreshes the list of respective tab.""" self.klist.clear() self.klist.add_list(self.populate()) def get_columns(self): """Returns a list of columns this tab should show.""" raise NotImplementedError def show_details(self): """Called when the details button is clicked. Displays the details of the selected object in the list.""" model = self.get_details_model(self.klist.get_selected()) run_dialog(self.get_details_dialog_class(), parent=self._parent, store=self._parent.store, model=model, visual_mode=True) def get_label(self): """Returns the name of the tab.""" label = gtk.Label(self.labels[1]) return label def get_details_model(self, model): """Subclassses can overwrite this method if the details dialog class needs a model different than the one on the list.""" return model def get_details_dialog_class(self): """Subclasses must return the dialog that should be displayed for more information about the item on the list""" return self.details_dialog_class def setup_widgets(self): """Override this if tab needs to do some custom widget setup.""" # # Callbacks # def _on_details_button__clicked(self, button): self.show_details() def _on_klist__row_activated(self, klist, item): self.show_details() def _on_klist__selection_changed(self, klist, data): self.button_box.details_button.set_sensitive(bool(data))
class DetailsTab(Gtk.VBox): details_dialog_class = None def __init__(self, model, parent): super(DetailsTab, self).__init__() self.model = model self._parent = parent self.set_spacing(6) self.set_border_width(6) self.klist = ObjectTree(self.get_columns()) self.populate() self.pack_start(self.klist, True, True, 0) self.klist.show() if len(self.klist) and self.get_details_dialog_class(): self.button_box = Gtk.HButtonBox() self.button_box.set_layout(Gtk.ButtonBoxStyle.START) details_button = Gtk.Button.new_with_label(self.details_lbl) self.button_box.pack_start(details_button, True, True, 0) details_button.set_sensitive(bool(self.klist.get_selected())) details_button.show() self.pack_end(self.button_box, False, False, 0) self.button_box.show() self.button_box.details_button = details_button details_button.connect('clicked', self._on_details_button__clicked) self.klist.connect('row-activated', self._on_klist__row_activated) self.klist.connect('selection-changed', self._on_klist__selection_changed) self.setup_widgets() def refresh(self): """Refreshes the list of respective tab.""" self.klist.clear() self.klist.add_list(self.populate()) def get_columns(self): """Returns a list of columns this tab should show.""" raise NotImplementedError def show_details(self): """Called when the details button is clicked. Displays the details of the selected object in the list.""" model = self.get_details_model(self.klist.get_selected()) run_dialog(self.get_details_dialog_class(), parent=self._parent, store=self._parent.store, model=model, visual_mode=True) def get_label(self): """Returns the name of the tab.""" label = Gtk.Label(label=self.labels[1]) return label def get_details_model(self, model): """Subclassses can overwrite this method if the details dialog class needs a model different than the one on the list.""" return model def get_details_dialog_class(self): """Subclasses must return the dialog that should be displayed for more information about the item on the list""" return self.details_dialog_class def setup_widgets(self): """Override this if tab needs to do some custom widget setup.""" # # Callbacks # def _on_details_button__clicked(self, button): self.show_details() def _on_klist__row_activated(self, klist, item): self.show_details() def _on_klist__selection_changed(self, klist, data): self.button_box.details_button.set_sensitive(bool(data))
class AdditionListSlave(SearchSlave): """A slave that offers a simple list and its management. This slave also has the option to display a small message right next to the buttons """ domain = 'stoq' toplevel_name = gladefile = 'AdditionListSlave' widgets = ('add_button', 'delete_button', 'klist', 'list_vbox', 'edit_button') gsignal('before-edit-item', object, retval=object) gsignal('on-edit-item', object) gsignal('on-add-item', object) gsignal('before-delete-items', object) gsignal('after-delete-items') def __init__(self, store, columns=None, editor_class=None, klist_objects=None, visual_mode=False, restore_name=None, tree=False): """ Creates a new AdditionListSlave object :param store: a store :param columns: column definitions :type columns: sequence of :class:`kiwi.ui.objectlist.Columns` :param editor_class: the window that is going to be open when user clicks on add_button or edit_button. :type: editor_class: a :class:`stoqlib.gui.editors.BaseEditor` subclass :param klist_objects: initial objects to insert into the list :param visual_mode: if we are working on visual mode, that means, not possible to edit the model on this object type visual_mode: bool :param restore_name: the name used to save and restore the columns on a cache system (e.g. pickle) :type restore_name: basestring :param tree: Indication of which kind of list we are adding. If `True` ObjectTree otherwise ObjectList will be added """ columns = columns or self.get_columns() SearchSlave.__init__(self, columns=columns, restore_name=restore_name, store=store) self.tree = tree self.klist = ObjectTree() if tree else ObjectList() self.list_vbox.add(self.klist) self.list_vbox.show_all() if not self.columns: raise StoqlibError("columns must be specified") self.visual_mode = visual_mode self.store = store self.set_editor(editor_class) self._can_edit = True self._callback_id = None if self.visual_mode: self.hide_add_button() self.hide_edit_button() self.hide_del_button() items = klist_objects or self.get_items() self._setup_klist(items) self._update_sensitivity() def _setup_klist(self, klist_objects): self.klist.set_columns(self.columns) self.klist.set_selection_mode(gtk.SELECTION_MULTIPLE) if self.tree: (self.klist.append(obj.parent_item, obj) for obj in klist_objects) else: self.klist.add_list(klist_objects) if self.visual_mode: self.klist.set_sensitive(False) def _update_sensitivity(self, *args): if self.visual_mode: return can_delete = _can_edit = True objs = self.get_selection() if not objs: _can_edit = can_delete = False elif len(objs) > 1: _can_edit = False self.add_button.set_sensitive(True) self.edit_button.set_sensitive(_can_edit) self.delete_button.set_sensitive(can_delete) def _edit_model(self, model=None, parent=None): edit_mode = model result = self.emit('before-edit-item', model) if result is None: result = self.run_editor(model) if not result: return if edit_mode: self.emit('on-edit-item', result) self.klist.update(result) else: if self.tree: self.klist.append(parent, result) else: self.klist.append(result) # Emit the signal after we added the item to the list to be able to # check the length of the list in our validation callbacks. self.emit('on-add-item', result) # As we have a selection extended mode for kiwi list, we # need to unselect everything before select the new instance. self.klist.unselect_all() self.klist.select(result) self._update_sensitivity() def _edit(self): if not self._can_edit: return objs = self.get_selection() qty = len(objs) if qty != 1: raise SelectionError( ("Please select only one item before choosing Edit." "\nThere are currently %d items selected") % qty) self._edit_model(objs[0]) def _clear(self): objs = self.get_selection() qty = len(objs) if qty < 1: raise SelectionError('There are no objects selected') msg = stoqlib_ngettext( _('Delete this item?'), _('Delete these %d items?') % qty, qty) delete_label = stoqlib_ngettext( _("Delete item"), _("Delete items"), qty) keep_label = stoqlib_ngettext( _("Keep it"), _("Keep them"), qty) if not yesno(msg, gtk.RESPONSE_NO, delete_label, keep_label): return self.emit('before-delete-items', objs) if qty == len(self.klist): self.klist.clear() else: for obj in objs: self.klist.remove(obj) self.klist.unselect_all() self._update_sensitivity() self.emit('after-delete-items') # # Hooks # def get_items(self): return [] def get_columns(self): raise NotImplementedError("get_columns must be implemented in " "subclasses") def run_editor(self, model): """This can be overriden to provide a custom run_dialog line, or a conversion function for the model """ if self._editor_class is None: raise TypeError( "%s cannot create or edit items without the editor_class " "argument set" % (self.__class__.__name__)) self.store.savepoint('before_run_editor_addition') retval = run_dialog(self._editor_class, None, store=self.store, model=model) if not retval: self.store.rollback_to_savepoint('before_run_editor_addition') return retval def delete_model(self, model): """Deletes a model, can be overridden in subclass :param model: model to delete """ model.__class__.delete(model.id, store=self.store) # # Public API # def add_extra_button(self, label=None, stock=None): """Add an extra button on the this slave The extra button will be appended at the end of the button box, the one containing the add/edit/delete buttons :param label: label of the button, can be ``None`` if stock is passed :param stock: stock label of the button, can be ``None`` if label is passed :param returns: the button added :rtype: gtk.Button """ if label is None and stock is None: raise TypeError("You need to provide a label or a stock argument") button = gtk.Button(label=label, stock=stock) button.set_property('can_focus', True) self.button_box.pack_end(button, False, False) button.show() return button def set_message(self, message, details_callback=None): """Display a simple message on a label, next to the add, edit, delete buttons :param message: a message with properly escaped markup """ self.message_hbox.set_visible(True) self.message_details_button.set_visible(bool(details_callback)) if details_callback: if self._callback_id: self.message_details_button.disconnect(self._callback_id) self._callback_id = self.message_details_button.connect( 'clicked', details_callback) self.message_label.set_markup(message) def clear_message(self): self.message_hbox.set_visible(False) def get_selection(self): # XXX: add get_selected_rows and raise exceptions if not in the # right mode if self.klist.get_selection_mode() == gtk.SELECTION_MULTIPLE: return self.klist.get_selected_rows() selection = self.klist.get_selected() if not selection: return [] return [selection] def hide_add_button(self): self.add_button.hide() def hide_edit_button(self): self._can_edit = False self.edit_button.hide() def hide_del_button(self): self.delete_button.hide() def set_editor(self, editor_class): if editor_class and not issubclass(editor_class, (BaseEditor, BaseWizard)): raise TypeError("editor_class must be a BaseEditor subclass") self._editor_class = editor_class # # Signal handlers # def on_klist__row_activated(self, *args): self._edit() def on_klist__selection_changed(self, *args): self._update_sensitivity() def on_add_button__clicked(self, button): self._edit_model() def on_edit_button__clicked(self, button): self._edit() def on_delete_button__clicked(self, button): self._clear()