class Editor(Gtk.HBox): """Base class for field editors Subclasses must define a list of operations and a datatype """ operations = [] data_type = None def __init__(self, store, field, other_fields): """ :param store: a storm store if its needed :param field: the field that is being edited :param other_fields: other fields available for math operations """ assert len(self.operations) self._store = store self._other_fields = other_fields self._oper = None self._field = field super(Editor, self).__init__(spacing=6) self.operations_combo = ProxyComboBox() self.pack_start(self.operations_combo, True, True, 0) self.operations_combo.connect('changed', self._on_operation_changed) for oper in self.operations: self.operations_combo.append_item(oper.label, oper) self.operations_combo.select(self.operations[0]) self.show_all() def set_field(self, field): assert field.data_type == self.data_type self._field = field self._oper.set_field(field) def _on_operation_changed(self, combo): if self._oper is not None: # Remove previous operation self.remove(self._oper) self._oper = combo.get_selected()(self._store, self._field, self._other_fields) self.pack_start(self._oper, True, True, 0) def apply_operation(self, item): return self._oper.apply_operation(item)
class StringSearchFilter(SearchFilter): """ Contains: - a label - an entry :ivar entry: the entry :ivar label: the label """ __gtype_name__ = 'StringSearchFilter' def __init__(self, label, chars=0, container=None): """ Create a new StringSearchFilter object. :param label: label of the search filter :param chars: maximum number of chars used by the search entry """ self._container = container SearchFilter.__init__(self, label=label) self.title_label = Gtk.Label(label=label) self.pack_start(self.title_label, False, False, 0) self.title_label.show() self._options = {} self.mode = ProxyComboBox() self.mode.connect('content-changed', self._on_mode__content_changed) self.pack_start(self.mode, False, False, 6) self.entry = Gtk.Entry() self.entry.set_placeholder_text(_("Search")) self.entry.props.secondary_icon_sensitive = False data = environ.get_resource_string('stoq', 'pixmaps', 'stoq-funnel-16x16.png') image = pixbuf_from_string(data) self.entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.PRIMARY, image) self.entry.set_icon_tooltip_text(Gtk.EntryIconPosition.PRIMARY, _("Add a filter")) self.entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, Gtk.STOCK_CLEAR) self.entry.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, _("Clear the search")) self.entry.connect("icon-release", self._on_entry__icon_release) self.entry.connect('activate', self._on_entry__activate) self.entry.connect('changed', self._on_entry__changed) if chars: self.entry.set_width_chars(chars) self.pack_start(self.entry, False, False, 6) self.entry.show() for option in [ ContainsAll, ContainsExactly, IdenticalTo, DoesNotContain ]: self._add_option(option) self.mode.select_item_by_position(0) def _add_option(self, option_type): option = option_type() self.mode.append_item(option.name, option_type) self._options[option_type] = option # # Callbacks # def _on_mode__content_changed(self, combo): self.emit('changed') def _on_entry__activate(self, entry): self.emit('changed') def _on_entry__changed(self, entry): entry.props.secondary_icon_sensitive = bool(entry.get_text()) def _position_filter_menu(self, data): window = self.entry.get_icon_window(Gtk.EntryIconPosition.PRIMARY) x, y = window.get_origin() y += window.get_size()[1] border = self.entry.style_get_property('progress-border') if border is not None: y += border.bottom return (x, y, True) def _on_entry__icon_release(self, entry, icon_pos, event): if icon_pos == Gtk.EntryIconPosition.SECONDARY: entry.set_text("") entry.grab_focus() self.emit('changed') elif icon_pos == Gtk.EntryIconPosition.PRIMARY: # We don't need create popup filters if haven't search columns. if (not self._container or not hasattr(self._container, 'menu') or not self._container.menu): return self._container.menu.popup(None, None, None, self._position_filter_menu, 0, event.time) # # SearchFilter # def get_state(self): option = self.mode.get_selected_data() return StringQueryState(filter=self, text=str(self.entry.get_text()), mode=option and option.mode) def set_state(self, text, mode=None): self.entry.set_text(text) if mode is not None: self.mode.select_item_by_position(mode) def get_title_label(self): return self.title_label def get_mode_combo(self): return self.mode def get_description(self): desc = self.entry.get_text() if desc: mode = self.mode.get_selected_label() return '%s %s "%s"' % ( self.title_label.get_text(), mode, desc, ) # # Public API # def enable_advanced(self): # Do not show the funnel icon if its an advanced filter self.entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.PRIMARY, None) self.mode.show() def set_label(self, label): self.title_label.set_text(label)
class MassEditorWidget(Gtk.HBox): _editors = { currency: DecimalEditor, Decimal: DecimalEditor, str: UnicodeEditor, datetime.date: DateEditor, object: ObjectEditor, } def __init__(self, store, fields, results): self._store = store self._editor = None self._fields = fields self._results = results super(MassEditorWidget, self).__init__(spacing=6) self._setup_widgets() def _filter_fields(self, data_type): return [f for f in self._fields if f.data_type == data_type] def _setup_editor(self, field): # Reuse editor if its possible (not when data_type is an object, since # that requires changing the reference values) if (self._editor and field.data_type is not object and self._editor.data_type == field.data_type): self._editor.set_field(field) return if self._editor: self.editor_placeholder.remove(self._editor) other_fields = self._filter_fields(field.data_type) klass = self._editors[field.data_type] self._editor = klass(self._store, field, other_fields) self.editor_placeholder.add(self._editor) def _setup_widgets(self): label = Gtk.Label(label=_('Update')) self.pack_start(label, False, False, 0) self.field_combo = ProxyComboBox() self.field_combo.connect('changed', self._on_field_combo__changed) self.pack_start(self.field_combo, False, False, 0) self.editor_placeholder = Gtk.EventBox() self.pack_start(self.editor_placeholder, False, False, 0) self.apply_button = Gtk.Button(stock=Gtk.STOCK_APPLY) self.apply_button.connect('clicked', self._on_apply_button__clicked) self.pack_start(self.apply_button, False, False, 0) for field in self._fields: # Don't let the user edit unique fields for now if field.unique or field.read_only: continue self.field_combo.append_item(field.label, field) self.field_combo.select_item_by_position(0) def _apply(self): marker('Updating values') for i in self._results: self._editor.apply_operation(i) self._results.refresh(i) marker('Done updating values') # # Public API # def get_changed_objects(self): """Returns a set of all the changed objects""" objs = set() for field in self._fields: objs.update(field.new_values.keys()) return objs # # BaseEditorSlave # def confirm(self, dialog): marker('Saving data') objs = self.get_changed_objects() total = len(objs) for i, obj in enumerate(objs): for field in self._fields: field.save_value(obj) yield i, total # Flush soon, so that any errors triggered by database constraints # pop up. self._store.flush() marker('Done saving data') # # Callbacks # def _on_field_combo__changed(self, combo): self._setup_editor(combo.get_selected()) def _on_apply_button__clicked(self, button): self._apply()
class StringSearchFilter(SearchFilter): """ Contains: - a label - an entry :ivar entry: the entry :ivar label: the label """ __gtype_name__ = 'StringSearchFilter' def __init__(self, label, chars=0, container=None): """ Create a new StringSearchFilter object. :param label: label of the search filter :param chars: maximum number of chars used by the search entry """ self._container = container SearchFilter.__init__(self, label=label) self.title_label = Gtk.Label(label=label) self.pack_start(self.title_label, False, False, 0) self.title_label.show() self._options = {} self.mode = ProxyComboBox() self.mode.connect('content-changed', self._on_mode__content_changed) self.pack_start(self.mode, False, False, 6) self.entry = Gtk.Entry() self.entry.set_placeholder_text(_("Search")) self.entry.props.secondary_icon_sensitive = False self.entry.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY, 'fa-filter-symbolic') self.entry.set_icon_tooltip_text(Gtk.EntryIconPosition.PRIMARY, _("Add a filter")) self.entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, 'edit-clear-symbolic') self.entry.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, _("Clear the search")) self.entry.connect("icon-release", self._on_entry__icon_release) self.entry.connect('activate', self._on_entry__activate) self.entry.connect('changed', self._on_entry__changed) if chars: self.entry.set_width_chars(chars) self.pack_start(self.entry, False, False, 6) self.entry.show() # Default filter will be only contains all. When advanced filter is enabled it will add # other options self._add_option(ContainsAll) self.mode.select_item_by_position(0) def _add_option(self, option_type): option = option_type() self.mode.append_item(option.name, option_type) self._options[option_type] = option # # Callbacks # def _on_mode__content_changed(self, combo): self.emit('changed') def _on_entry__activate(self, entry): self.emit('changed') def _on_entry__changed(self, entry): entry.props.secondary_icon_sensitive = bool(entry.get_text()) def _position_filter_menu(self, data): window = self.entry.get_icon_window(Gtk.EntryIconPosition.PRIMARY) x, y = window.get_origin() y += window.get_size()[1] border = self.entry.style_get_property('progress-border') if border is not None: y += border.bottom return (x, y, True) def _on_entry__icon_release(self, entry, icon_pos, event): if icon_pos == Gtk.EntryIconPosition.SECONDARY: entry.set_text("") entry.grab_focus() self.emit('changed') elif icon_pos == Gtk.EntryIconPosition.PRIMARY: # We don't need create popup filters if haven't search columns. if (not self._container or not hasattr(self._container, 'menu') or not self._container.menu): return self._container.menu.popup(None, None, None, self._position_filter_menu, 0, event.time) # # SearchFilter # def get_state(self): option = self.mode.get_selected_data() return StringQueryState(filter=self, text=str(self.entry.get_text()), mode=option and option.mode) def set_state(self, text, mode=None): self.entry.set_text(text) for i in self.mode.get_model_items().values(): if i.mode == mode: self.mode.select_item_by_data(i) break def get_title_label(self): return self.title_label def get_mode_combo(self): return self.mode def get_description(self): desc = self.entry.get_text() if desc: mode = self.mode.get_selected_label() return '%s %s "%s"' % (self.title_label.get_text(), mode, desc,) # # Public API # def enable_advanced(self): # Do not show the funnel icon if its an advanced filter self.entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.PRIMARY, None) for option in [ContainsExactly, IdenticalTo, DoesNotContain]: self._add_option(option) self.mode.show() def set_label(self, label): self.title_label.set_text(label)