def _setup_entry_slave(self, box=None): widget = ProxyEntry() # Try to simulate insensitive appearance for non-editable entries # while keeping them selectable widget.set_editable(self.sensitive) if not self.sensitive: style = widget.get_style() widget.modify_text(gtk.STATE_NORMAL, style.text[gtk.STATE_INSENSITIVE]) widget.modify_base(gtk.STATE_NORMAL, style.base[gtk.STATE_INSENSITIVE]) widget.data_type = unicode widget.model_attribute = "field_value" self.proxy.add_widget("field_value", widget) if box is None: self.container.add(widget) else: box.pack_start(widget) widget.show() widget.connect('validate', self._on_entry__validate) widget.connect('validation-changed', self._on_entry__validation_changed) self._entry = widget
def __init__(self): entry = ProxyEntry() ComboEntry.__init__(self, entry=entry) ValidatableProxyWidgetMixin.__init__(self) entry.connect('content-changed', self._on_entry__content_changed) entry.connect('validation-changed', self._on_entry__validation_changed)
def _setup_entry_slave(self, box=None): widget = ProxyEntry() # Try to simulate insensitive appearance for non-editable entries # while keeping them selectable widget.set_editable(self.sensitive) if not self.sensitive: style = widget.get_style() widget.modify_text( gtk.STATE_NORMAL, style.text[gtk.STATE_INSENSITIVE]) widget.modify_base( gtk.STATE_NORMAL, style.base[gtk.STATE_INSENSITIVE]) widget.data_type = unicode widget.model_attribute = "field_value" self.proxy.add_widget("field_value", widget) if box is None: self.container.add(widget) else: box.pack_start(widget) widget.show() widget.connect('validate', self._on_entry__validate) widget.connect('validation-changed', self._on_entry__validation_changed) self._entry = widget
class EditorPropertyName(EditorProperty): duplicate_name_msg = _("There is already a widget with this name") no_name_msg = _("The widget must have a name") def __init__(self, property_class, app): EditorProperty.__init__(self, property_class, app) self.input = ProxyEntry() self.input.set_property('data-type', str) self.input.connect('changed', self._changed_text) def load(self, widget): self._start_load(widget) self._internal_load(widget) self._finish_load() def _internal_load(self, widget): value = self.property.value pos = self.input.get_position() self.input.delete_text(0, -1) if value: self.input.insert_text(value) self.input.set_position(pos) # validate that the name exists and is unique if not value: self.input.set_invalid(self.no_name_msg) elif self._project.get_gadget_by_name(value) != widget: self.input.set_invalid(self.duplicate_name_msg) else: self.input.set_valid() def _changed_text(self, entry): """ Update the name property of the widget if the name is valid. """ if self.property_loading: return name = entry.get_chars(0, -1) if not name: self.input.set_invalid(self.no_name_msg) elif self._project.get_gadget_by_name(name): self.input.set_invalid(self.duplicate_name_msg) else: self.input.set_valid() self.set(name)
def _setup_entry_slave(self, box=None): widget = ProxyEntry() widget.props.sensitive = self.sensitive widget.data_type = unicode widget.model_attribute = "field_value" self.proxy.add_widget("field_value", widget) if box is None: self.container.add(widget) else: box.pack_start(widget) widget.show() widget.connect("validate", self._on_entry__validate) widget.connect("validation-changed", self._on_entry__validation_changed) self._entry = widget
def _create_entry(self, mandatory=False): entry = ProxyEntry() entry.data_type = unicode # Set as empty or kiwi will return ValueUnset on entry.read() # and we would have to take that in consideration everywhere here entry.update(u'') entry.mandatory = mandatory self.setup_entry(entry) entry.connect_after('content-changed', self._after_entry__content_changed) entry.connect_after('changed', self._after_entry__changed) entry.connect('validate', self._on_entry__validate) entry.connect('activate', self._on_entry__activate) return entry
def _setup_entry_slave(self, box=None): widget = ProxyEntry() widget.props.sensitive = self.sensitive widget.data_type = unicode widget.model_attribute = "field_value" self.proxy.add_widget("field_value", widget) if box is None: self.container.add(widget) else: box.pack_start(widget) widget.show() widget.connect('validate', self._on_entry__validate) widget.connect('validation-changed', self._on_entry__validation_changed) self._entry = widget
def _create_entry(self, mandatory=False): entry = ProxyEntry() entry.data_type = str # Set as empty or kiwi will return ValueUnset on entry.read() # and we would have to take that in consideration everywhere here entry.update(u'') entry.mandatory = mandatory self.setup_entry(entry) entry.connect_after('content-changed', self._after_entry__content_changed) entry.connect_after('changed', self._after_entry__changed) entry.connect('validate', self._on_entry__validate) entry.connect('activate', self._on_entry__activate) return entry
def _setup_entry_slave(self, box=None): widget = ProxyEntry() # Try to simulate insensitive appearance for non-editable entries # while keeping them selectable widget.set_editable(self.sensitive) if not self.sensitive: sc = widget.get_style_context() sc.add_class('visualmode') widget.data_type = str widget.model_attribute = "field_value" self.proxy.add_widget("field_value", widget) if box is None: self.container.add(widget) else: box.pack_start(widget, True, True, 0) widget.show() widget.connect('validate', self._on_entry__validate) widget.connect('validation-changed', self._on_entry__validation_changed) self._entry = widget
def test_validate(self): def validate_entry(entry, value): if value == 100: return ValidationError() # FIXME: For some reason, entry is not emitting 'changed' event # on set_text, not even if we call entry.emit('changed') by hand. # That only happens here on the test. Figure out why def update_entry(entry, value): entry.set_text(value) entry.validate(force=True) entry = ProxyEntry() entry.data_type = currency entry.connect('validate', validate_entry) entry.set_text('150') calc = CalculatorPopup(entry, CalculatorPopup.MODE_SUB) # calc.popup will not work here, so call _update_ui directly calc._update_ui() self.assertValid(calc, ['_entry']) self.assertNotVisible(calc, ['_warning']) for value in ['abc', '+10%', '-10%', '+10', '-10']: update_entry(calc._entry, value) self.assertInvalid(calc, ['_entry']) self.assertNotVisible(calc, ['_warning']) update_entry(calc._entry, '40') self.assertValid(calc, ['_entry']) self.assertNotVisible(calc, ['_warning']) # 50 of discount will make the value invalid on entry # (see validate_entry above) update_entry(calc._entry, '50') self.assertValid(calc, ['_entry']) self.assertVisible(calc, ['_warning'])
def test_validate(self): def validate_entry(entry, value): if value == 100: return ValidationError() # FIXME: For some reason, entry is not emitting 'changed' event # on set_text, not even if we call entry.emit('changed') by hand. # That only happens here on the test. Figure out why def update_entry(entry, value): entry.set_text(value) entry.validate(force=True) entry = ProxyEntry() entry.data_type = currency entry.connect('validate', validate_entry) entry.set_text('150') calc = CalculatorPopup(entry, CalculatorPopup.MODE_SUB) # calc.popup will not work here, so call validate_popup directly calc.validate_popup() self.assertValid(calc, ['_entry']) self.assertNotVisible(calc, ['_warning']) for value in ['abc', '+10%', '-10%', '+10', '-10']: update_entry(calc._entry, value) self.assertInvalid(calc, ['_entry']) self.assertNotVisible(calc, ['_warning']) update_entry(calc._entry, '40') self.assertValid(calc, ['_entry']) self.assertNotVisible(calc, ['_warning']) # 50 of discount will make the value invalid on entry # (see validate_entry above) update_entry(calc._entry, '50') self.assertValid(calc, ['_entry']) self.assertVisible(calc, ['_warning'])
class DateTimeEditUI(GladeSlaveDelegate): def __init__(self, dt = None): if not dt: self.dt = datetime.datetime.now() else: self.dt = dt # Set up the user interface GladeSlaveDelegate.__init__(self, gladefile="mo_datetime_edit", toplevel_name="window_main") self.entry_date = DateEntry() self.entry_time = ProxyEntry(data_type=datetime.time) self.set_datetime(self.dt) self.entry_date.connect('changed', self.entry_date__changed) self.entry_time.connect('changed', self.entry_time__changed) self.hbox.pack_start(self.entry_date, expand=False, fill=False) self.hbox.pack_start(self.entry_time, expand=False, fill=False) self.show() def entry_date__changed(self, *args): dt = self.entry_date.get_date() if dt != None: self.dt = self.dt.replace(dt.year, dt.month, dt.day) def entry_time__changed(self, *args): dt = None try: dt = self.entry_time.read() except ValidationError, e: # Ignore invalid times because the user might still be filling it # out. pass if dt != None: self.dt = self.dt.replace(self.dt.year, self.dt.month, self.dt.day, dt.hour, dt.minute)
def on_entry_activate(entry): print('You selected:', entry.read()) Gtk.main_quit() win = Gtk.Window() win.connect('delete-event', Gtk.main_quit) vbox = Gtk.VBox() win.add(vbox) # Normal entry entry = ProxyEntry() entry.data_type = str entry.connect('activate', on_entry_activate) entry.prefill([ 'Belo Horizonte', 'São Carlos', 'São Paulo', 'Båstad', 'Örnsköldsvik', 'sanca', 'sampa' ]) vbox.pack_start(entry, True, True, 0) entry = ProxyEntry() entry.data_type = int entry.connect('activate', on_entry_activate) entry.prefill([('Brazil', 186), ('Sweden', 9), ('China', 1306)]) vbox.pack_start(entry, True, True, 0) win.show_all() Gtk.main()
class DateEntry(gtk.HBox): """I am an entry which you can input a date on. In addition to an gtk.Entry I also contain a button with an arrow you can click to get popup window with a gtk.Calendar for which you can use to select the date """ gsignal('changed') gsignal('activate') def __init__(self): gtk.HBox.__init__(self) self._popping_down = False self._old_date = None # bootstrap problems, kiwi.ui.widgets.entry imports dateentry # we need to use a proxy entry because we want the mask from kiwi.ui.widgets.entry import ProxyEntry self.entry = ProxyEntry() self.entry.connect('changed', self._on_entry__changed) self.entry.connect('activate', self._on_entry__activate) self.entry.set_property('data-type', datetime.date) mask = self.entry.get_mask() if mask: self.entry.set_width_chars(len(mask)) self.pack_start(self.entry, False, False) self.entry.show() self._button = gtk.ToggleButton() self._button.connect('scroll-event', self._on_entry__scroll_event) self._button.connect('toggled', self._on_button__toggled) self._button.set_focus_on_click(False) self.pack_start(self._button, False, False) self._button.show() arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_NONE) self._button.add(arrow) arrow.show() self._popup = _DateEntryPopup(self) self._popup.connect('date-selected', self._on_popup__date_selected) self._popup.connect('hide', self._on_popup__hide) self._popup.set_size_request(-1, 24) # Virtual methods def do_grab_focus(self): self.entry.grab_focus() # Callbacks def _on_entry__changed(self, entry): try: date = self.get_date() except ValidationError: date = None self._changed(date) def _on_entry__activate(self, entry): self.emit('activate') def _on_entry__scroll_event(self, entry, event): if event.direction == gdk.SCROLL_UP: days = 1 elif event.direction == gdk.SCROLL_DOWN: days = -1 else: return try: date = self.get_date() except ValidationError: date = None if not date: newdate = datetime.date.today() else: newdate = date + datetime.timedelta(days=days) self.set_date(newdate) def _on_button__toggled(self, button): if self._popping_down: return try: date = self.get_date() except ValidationError: date = None self._popup.popup(date) def _on_popup__hide(self, popup): self._popping_down = True self._button.set_active(False) self._popping_down = False def _on_popup__date_selected(self, popup, date): self.set_date(date) popup.popdown() self.entry.grab_focus() self.entry.set_position(len(self.entry.get_text())) self._changed(date) def _changed(self, date): if self._old_date != date: self.emit('changed') self._old_date = date # Public API def set_date(self, date): """Sets the date. @param date: date to set @type date: a datetime.date instance or None """ if not isinstance(date, datetime.date) and date is not None: raise TypeError( "date must be a datetime.date instance or None, not %r" % ( date,)) if date is None: value = '' else: value = date_converter.as_string(date) self.entry.set_text(value) def get_date(self): """Get the selected date @returns: the date. @rtype: datetime.date or None """ try: date = self.entry.read() except ValidationError: date = None if date == ValueUnset: date = None return date
class CalculatorPopup(PopupWindow): """A popup calculator for entries Right now it supports both :class:`kiwi.ui.widgets.spinbutton.ProxySpinButton` and :class:`kiwi.ui.widgets.entry.ProxyEntry`, as long as their data types are numeric (e.g. int, currency, Decimal, etc) """ #: The add mode. Any value typed on the entry will be added to the #: original value. e.g. 10% means +10% MODE_ADD = 0 #: The sub mode. Any value typed on the entry will be subtracted from the #: original value. e.g. 10% means -10% MODE_SUB = 1 _mode = None _data_type_mapper = { 'currency': currency, 'Decimal': decimal.Decimal, } def __init__(self, entry, mode): """ :param entry: a :class:`kiwi.ui.widgets.spinbutton.ProxySpinButton` or a :class:`kiwi.ui.widgets.entry.ProxyEntry` subclass :param mode: one of :attr:`.MODE_ADD`, :attr:`.MODE_SUB` """ self._mode = mode self._new_value = None self._data_type = self._data_type_mapper[entry.data_type] self._converter = converter.get_converter(self._data_type) super(CalculatorPopup, self).__init__(entry) # # Public API # def get_main_widget(self): # This is done on entry to check where to put the validation/mandatory # icons. We should put the calculator on the other side. # Note that spinbuttons are always right aligned and thus # xalign will always be 1.0 if self.attached_widget.get_alignment() > 0.5: self._icon_pos = 'secondary-icon' else: self._icon_pos = 'primary-icon' self.attached_widget.set_property(self._icon_pos + '-activatable', True) self.attached_widget.set_property( self._icon_pos + '-tooltip-text', _("Do calculations on top of this value")) self.attached_widget.connect( 'notify::sensitive', self._on_entry_sensitive__notify) self.attached_widget.connect('icon-press', self._on_entry__icon_press) self._toggle_calculator_icon() vbox = Gtk.VBox(spacing=6) vbox.show() self._main_label = Gtk.Label() self._main_label.set_ellipsize(Pango.EllipsizeMode.END) vbox.pack_start(self._main_label, True, True, 0) self._main_label.show() self._entry = ProxyEntry() # FIXME: We need a model_attribute here or else the entry.is_valid() # will always return None. Check proxywidget.py's FIXME to see why self._entry.model_attribute = 'not_used' self._entry.connect('validate', self._on_entry__validate) self._entry.connect_after('changed', self._after_entry__changed) self._entry.set_alignment(1.0) vbox.pack_start(self._entry, True, True, 0) self._entry.show() hbox = Gtk.HBox(spacing=6) vbox.pack_start(hbox, True, True, 0) hbox.show() self._label = Gtk.Label() self._label.set_property('xalign', 1.0) self._label.set_use_markup(True) hbox.pack_start(self._label, True, True, 0) self._label.show() self._warning = Gtk.Image() hbox.pack_start(self._warning, False, False, 0) return vbox def validate_popup(self): try: self._new_value = self._data_type(self.attached_widget.get_text()) except decimal.InvalidOperation: return False self._entry.set_text('') self._entry.set_tooltip_text(_("Use absolute or percentage (%) value")) self._preview_new_value() self._main_label.set_text(self._get_main_label()) return True def confirm(self): self._maybe_apply_new_value() # # Private # def _get_main_label(self): if self._mode == self.MODE_ADD: return (_("Surcharge") if self._data_type == currency else _("Addition")) elif self._mode == self.MODE_SUB: return (_("Discount") if self._data_type == currency else _("Subtraction")) else: raise AssertionError def _set_warning(self, warning): if warning is None: self._warning.hide() else: self._warning.set_from_stock(Gtk.STOCK_DIALOG_WARNING, Gtk.IconSize.MENU) self._warning.set_tooltip_text(warning) self._warning.show() def _get_new_value(self): operation = self._entry.get_text().strip() operation = operation.replace(',', '.') if operation.endswith('%'): op_value = operation[:-1] percentage = True else: op_value = operation percentage = False if not operation: return if operation[0] in ['+', '-']: raise ValueError(_("Operator signals are not supported...")) if self._mode == self.MODE_SUB: op = operator.sub elif self._mode == self.MODE_ADD: op = operator.add try: op_value = decimal.Decimal(op_value) except decimal.InvalidOperation: raise ValueError( _("'%s' is not a valid operation...") % (operation,)) if percentage: value = op(self._new_value, self._new_value * (op_value / 100)) else: value = op(self._new_value, op_value) return value def _update_new_value(self): if not self._entry.is_valid(): return self._new_value = self._get_new_value() self._entry.set_text('') self._preview_new_value() def _preview_new_value(self): self._label.set_markup('<b>%s</b>' % ( self._converter.as_string(self._new_value), )) def _maybe_apply_new_value(self): if self._entry.get_text(): self._update_new_value() return self.attached_widget.update(self._new_value) self.popdown() def _toggle_calculator_icon(self): if self.attached_widget.get_sensitive(): icon = STOQ_CALC else: icon = None self.attached_widget.set_property(self._icon_pos + '-name', icon) # # Callbacks # def _on_entry__validate(self, entry, value): try: value = self._get_new_value() except ValueError as err: return ValidationError('%s\n%s' % (err, _("Use absolute or percentage (%) value"))) if value: warning = self.attached_widget.emit('validate', value) warning = warning and str(warning) else: warning = None self._set_warning(warning) def _after_entry__changed(self, entry): entry.validate(force=True) def _on_entry_sensitive__notify(self, obj, pspec): self._toggle_calculator_icon() def _on_entry__icon_press(self, entry, icon_pos, event): if icon_pos != Gtk.EntryIconPosition.SECONDARY: return self.popup()
class CalculatorPopup(PopupWindow): """A popup calculator for entries Right now it supports both :class:`kiwi.ui.widgets.spinbutton.ProxySpinButton` and :class:`kiwi.ui.widgets.entry.ProxyEntry`, as long as their data types are numeric (e.g. int, currency, Decimal, etc) """ #: The add mode. Any value typed on the entry will be added to the #: original value. e.g. 10% means +10% MODE_ADD = 0 #: The sub mode. Any value typed on the entry will be subtracted from the #: original value. e.g. 10% means -10% MODE_SUB = 1 _mode = None _data_type_mapper = { 'currency': currency, 'Decimal': decimal.Decimal, } def __init__(self, entry, mode): """ :param entry: a :class:`kiwi.ui.widgets.spinbutton.ProxySpinButton` or a :class:`kiwi.ui.widgets.entry.ProxyEntry` subclass :param mode: one of :attr:`.MODE_ADD`, :attr:`.MODE_SUB` """ self._mode = mode self._new_value = None self._data_type = self._data_type_mapper[entry.data_type] self._converter = converter.get_converter(self._data_type) super(CalculatorPopup, self).__init__(entry) # # Public API # def get_main_widget(self): # This is done on entry to check where to put the validation/mandatory # icons. We should put the calculator on the other side. # Note that spinbuttons are always right aligned and thus # xalign will always be 1.0 if self.attached_widget.get_alignment() > 0.5: self._icon_pos = 'secondary-icon' else: self._icon_pos = 'primary-icon' self.attached_widget.set_property(self._icon_pos + '-activatable', True) self.attached_widget.set_property( self._icon_pos + '-tooltip-text', _("Do calculations on top of this value")) self.attached_widget.connect('notify::sensitive', self._on_entry_sensitive__notify) self.attached_widget.connect('icon-press', self._on_entry__icon_press) self._toggle_calculator_icon() vbox = Gtk.VBox(spacing=6) vbox.show() self._main_label = Gtk.Label() self._main_label.set_ellipsize(Pango.EllipsizeMode.END) vbox.pack_start(self._main_label, True, True, 0) self._main_label.show() self._entry = ProxyEntry() # FIXME: We need a model_attribute here or else the entry.is_valid() # will always return None. Check proxywidget.py's FIXME to see why self._entry.model_attribute = 'not_used' self._entry.connect('validate', self._on_entry__validate) self._entry.connect_after('changed', self._after_entry__changed) self._entry.set_alignment(1.0) vbox.pack_start(self._entry, True, True, 0) self._entry.show() hbox = Gtk.HBox(spacing=6) vbox.pack_start(hbox, True, True, 0) hbox.show() self._label = Gtk.Label() self._label.set_property('xalign', 1.0) self._label.set_use_markup(True) hbox.pack_start(self._label, True, True, 0) self._label.show() self._warning = Gtk.Image() hbox.pack_start(self._warning, False, False, 0) return vbox def validate_popup(self): try: self._new_value = self._data_type(self.attached_widget.get_text()) except decimal.InvalidOperation: return False self._entry.set_text('') self._entry.set_tooltip_text(_("Use absolute or percentage (%) value")) self._preview_new_value() self._main_label.set_text(self._get_main_label()) return True def confirm(self): self._maybe_apply_new_value() # # Private # def _get_main_label(self): if self._mode == self.MODE_ADD: return (_("Surcharge") if self._data_type == currency else _("Addition")) elif self._mode == self.MODE_SUB: return (_("Discount") if self._data_type == currency else _("Subtraction")) else: raise AssertionError def _set_warning(self, warning): if warning is None: self._warning.hide() else: self._warning.set_from_stock(Gtk.STOCK_DIALOG_WARNING, Gtk.IconSize.MENU) self._warning.set_tooltip_text(warning) self._warning.show() def _get_new_value(self): operation = self._entry.get_text().strip() operation = operation.replace(',', '.') if operation.endswith('%'): op_value = operation[:-1] percentage = True else: op_value = operation percentage = False if not operation: return if operation[0] in ['+', '-']: raise ValueError(_("Operator signals are not supported...")) if self._mode == self.MODE_SUB: op = operator.sub elif self._mode == self.MODE_ADD: op = operator.add try: op_value = decimal.Decimal(op_value) except decimal.InvalidOperation: raise ValueError( _("'%s' is not a valid operation...") % (operation, )) if percentage: value = op(self._new_value, self._new_value * (op_value / 100)) else: value = op(self._new_value, op_value) return value def _update_new_value(self): if not self._entry.is_valid(): return self._new_value = self._get_new_value() self._entry.set_text('') self._preview_new_value() def _preview_new_value(self): self._label.set_markup('<b>%s</b>' % (self._converter.as_string(self._new_value), )) def _maybe_apply_new_value(self): if self._entry.get_text(): self._update_new_value() return self.attached_widget.update(self._new_value) self.popdown() def _toggle_calculator_icon(self): if self.attached_widget.get_sensitive(): pixbuf = self.render_icon(STOQ_CALC, Gtk.IconSize.MENU) else: pixbuf = None self.attached_widget.set_property(self._icon_pos + '-pixbuf', pixbuf) # # Callbacks # def _on_entry__validate(self, entry, value): try: value = self._get_new_value() except ValueError as err: return ValidationError( '%s\n%s' % (err, _("Use absolute or percentage (%) value"))) if value: warning = self.attached_widget.emit('validate', value) warning = warning and str(warning) else: warning = None self._set_warning(warning) def _after_entry__changed(self, entry): entry.validate(force=True) def _on_entry_sensitive__notify(self, obj, pspec): self._toggle_calculator_icon() def _on_entry__icon_press(self, entry, icon_pos, event): if icon_pos != Gtk.EntryIconPosition.SECONDARY: return self.popup()
from kiwi.ui.widgets.entry import ProxyEntry def on_entry_activate(entry): print 'You selected:', entry.read() gtk.main_quit() win = gtk.Window() win.connect('delete-event', gtk.main_quit) vbox = gtk.VBox() win.add(vbox) # Normal entry entry = ProxyEntry() entry.data_type = str entry.connect('activate', on_entry_activate) entry.prefill(['Belo Horizonte', u'São Carlos', u'São Paulo', u'Båstad', u'Örnsköldsvik', 'sanca', 'sampa']) vbox.pack_start(entry) entry = ProxyEntry() entry.data_type = int entry.connect('activate', on_entry_activate) entry.prefill([('Brazil', 186), ('Sweden', 9), ('China', 1306)]) vbox.pack_start(entry) win.show_all() gtk.main()
class AccountEditor(BaseEditor): """ Account Editor """ gladefile = "AccountEditor" proxy_widgets = ['description', 'code'] model_type = Account model_name = _('Account') def __init__(self, store, model=None, parent_account=None): self._last_account_type = None self._bank_number = -1 self._bank_widgets = [] self._bank_option_widgets = [] self._option_fields = {} self._test_button = None self.existing = model is not None self.parent_account = parent_account self.bank_model = _TemporaryBankAccount() BaseEditor.__init__(self, store, model) action_area = self.main_dialog.action_area action_area.set_layout(Gtk.ButtonBoxStyle.END) action_area.pack_start(self._test_button, False, False, 0) action_area.set_child_secondary(self._test_button, True) self._test_button.show() # # BaseEditor hooks # def create_model(self, store): return Account(description=u"", account_type=Account.TYPE_CASH, store=store) def _setup_widgets(self): self._test_button = Gtk.Button(_("Print a test bill")) self._test_button.connect('clicked', self._on_test_button__clicked) self.parent_accounts = AccountTree(with_code=False, create_mode=True) self.parent_accounts.connect( 'selection-changed', self._on_parent_accounts__selection_changed) self.tree_box.pack_start(self.parent_accounts, True, True, 0) self.tree_box.reorder_child(self.parent_accounts, 0) if sysparam.compare_object('IMBALANCE_ACCOUNT', self.model): self.account_type.set_sensitive(False) self.account_type.prefill(Account.account_type_descriptions) account_type = self.model.account_type self.parent_accounts.insert_initial(self.store, edited_account=self.model) if self.parent_account: account = self.parent_accounts.get_account_by_id( self.parent_account.id) self.parent_accounts.select(account) if not self.existing: account_type = account.account_type self.account_type.select(account_type) self.parent_accounts.show() def setup_proxies(self): self._setup_widgets() self.add_proxy(self.model, AccountEditor.proxy_widgets) def validate_confirm(self): if not self.model.description: return False account = self.parent_accounts.get_selected() if not account: return True return account.selectable def on_confirm(self): new_parent = self.parent_accounts.get_selected() if new_parent: new_parent = new_parent.account if new_parent != self.model: self.model.parent = new_parent self.model.account_type = self.account_type.get_selected() self._save_bank() def refresh_ok(self, value): BaseEditor.refresh_ok(self, value) account_type = self.account_type.get_selected() if account_type != Account.TYPE_BANK: value = False self._test_button.set_sensitive(value) # Private def _save_bank(self): bank_account = self.model.bank if not bank_account: bank_account = BankAccount(account=self.model, store=self.store) # FIXME: Who sets this to a str? bank_account.bank_account = unicode(self.bank_model.bank_account) bank_account.bank_branch = unicode(self.bank_model.bank_branch) if self._bank_number is not None: bank_account.bank_number = self.bank_model.bank_number self._save_bank_bill_options(bank_account) def _save_bank_bill_options(self, bank_account): for option, entry in self._option_fields.items(): value = unicode(entry.get_text()) bill_option = self.store.find(BillOption, bank_account=bank_account, option=option).one() if bill_option is None: bill_option = BillOption(store=self.store, bank_account=bank_account, option=option, value=value) bill_option.value = value def _add_widget(self, label, widget, options=False): n_rows = self.table.props.n_rows l = Gtk.Label() l.set_markup(label) l.props.xalign = 1.0 self.table.resize(n_rows + 1, 2) self.table.attach(l, 0, 1, n_rows, n_rows + 1, Gtk.AttachOptions.FILL, 0, 0, 0) self.table.attach(widget, 1, 2, n_rows, n_rows + 1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0, 0, 0) if options: self._bank_option_widgets.extend([l, widget]) else: self._bank_widgets.extend([l, widget]) l.show() return l def _update_bank_type(self): self._remove_bank_option_widgets() bank_number = self.bank_type.get_selected() bank_info = None if bank_number: bank_info = get_bank_info_by_number(bank_number) self.bank_number = ProxyEntry() self.bank_number.props.data_type = int self.bank_number.set_sensitive(False) bank_number_lbl = self._add_widget(api.escape(_("Number:")), self.bank_number, options=True) self.bank_branch = ProxyEntry() self.bank_branch.connect('validate', self._on_bank_branch__validate, bank_info) self.bank_branch.props.data_type = 'str' self.bank_branch.props.mandatory = True self.bank_branch.model_attribute = "bank_branch" bank_branch_lbl = self._add_widget(api.escape(_("Agency:")), self.bank_branch, options=True) if bank_number is not None: bank_branch_lbl.show() self.bank_branch.show() else: bank_branch_lbl.hide() self.bank_account = ProxyEntry() self.bank_account.connect('validate', self._on_bank_account__validate, bank_info) self._add_widget(api.escape(_("Account:")), self.bank_account, options=True) self.bank_account.model_attribute = "bank_account" self.bank_account.props.data_type = 'str' if bank_number is not None: self.bank_account.props.mandatory = True self.bank_account.show() attributes = ['bank_account', 'bank_branch', 'bank_number'] if bank_number is not None: bank_number_lbl.show() self.bank_number.show() self.bank_model.bank_number = bank_number for i, option in enumerate(bank_info.get_extra_options()): name = 'option' + str(i) entry = ProxyEntry() entry.model_attribute = name setattr(self, name, entry) # Set the model attr too so it can be validated setattr(self.bank_model, name, u'') entry.props.data_type = 'str' entry.connect('validate', self._on_bank_option__validate, bank_info, option) name = option.replace('_', ' ').capitalize() self._add_widget("<i>%s</i>:" % api.escape(name), entry, options=True) entry.show() self._option_fields[option] = entry attributes.append(entry.model_attribute) else: bank_number_lbl.hide() self.bank_proxy = self.add_proxy(self.bank_model, attributes) self._fill_bank_account() def _fill_bank_account(self): if not self.model.bank: return self.bank_model.bank_branch = self.model.bank.bank_branch.encode( 'utf-8') self.bank_model.bank_account = self.model.bank.bank_account.encode( 'utf-8') self.bank_proxy.update('bank_branch') self.bank_proxy.update('bank_account') bill_options = list( self.store.find(BillOption, bank_account=self.model.bank)) for bill_option in bill_options: if bill_option.option is None: continue field_entry = self._option_fields.get(bill_option.option) if field_entry: field_entry.set_text(bill_option.value) def _update_account_type(self, account_type): if not self.account_type.get_sensitive(): return if account_type != Account.TYPE_BANK: self._remove_bank_widgets() self._remove_bank_option_widgets() self.code.set_sensitive(True) return self.code.set_sensitive(False) self.bank_type = ProxyComboBox() self._add_widget(api.escape(_("Bank:")), self.bank_type) self.bank_type.connect('content-changed', self._on_bank_type__content_changed) self.bank_type.show() banks = [(_('Generic'), None)] banks.extend([(b.description, b.bank_number) for b in get_all_banks()]) self.bank_type.prefill(banks) if self.model.bank: try: self.bank_type.select(self.model.bank.bank_number) except KeyError: self.bank_type.select(None) self._update_bank_type() def _remove_bank_widgets(self): for widget in self._bank_widgets: widget.get_parent().remove(widget) widget.destroy() self.table.resize(5, 2) self._bank_widgets = [] def _remove_bank_option_widgets(self): for widget in self._bank_option_widgets: widget.get_parent().remove(widget) widget.destroy() self.table.resize(5 + len(self._bank_widgets) / 2, 2) self._bank_option_widgets = [] self._option_fields = {} def _print_test_bill(self): try: bank_info = get_bank_info_by_number(self.bank_model.bank_number) except NotImplementedError: info(_("This bank does not support printing of bills")) return kwargs = dict( valor_documento=12345.67, data_vencimento=datetime.date.today(), data_documento=datetime.date.today(), data_processamento=datetime.date.today(), nosso_numero=u'24533', numero_documento=u'1138', sacado=[_(u"Drawee"), _(u"Address"), _(u"Details")], cedente=[_(u"Supplier"), _(u"Address"), _(u"Details"), ("CNPJ")], demonstrativo=[_(u"Demonstration")], instrucoes=[_(u"Instructions")], agencia=self.bank_model.bank_branch, conta=self.bank_model.bank_account, ) for opt in self.bank_model.options: kwargs[opt.option] = opt.value data = bank_info(**kwargs) print_report(BillTestReport, data) # Callbacks def _on_parent_accounts__selection_changed(self, objectlist, account): self.force_validation() def on_description__activate(self, entry): if self.validate_confirm(): self.confirm() def on_description__validate(self, entry, text): if not text: return ValidationError(_("Account description cannot be empty")) def on_account_type__content_changed(self, account_type): account_type = account_type.get_selected() if self._last_account_type == account_type: return self._update_account_type(account_type) self._last_account_type = account_type def _on_bank_type__content_changed(self, bank_type): bank_number = bank_type.get_selected() if self._bank_number == bank_number: return self._update_bank_type() self._bank_number = bank_number def _on_bank_branch__validate(self, entry, value, bank_info): if bank_info: try: bank_info.validate_field(value) except BoletoException as e: return ValidationError(str(e)) def _on_bank_account__validate(self, entry, value, bank_info): if bank_info: try: bank_info.validate_field(value) except BoletoException as e: return ValidationError(str(e)) def _on_bank_option__validate(self, entry, value, bank_info, option): try: bank_info.validate_option(option, value) except BoletoException as e: return ValidationError(str(e)) self.bank_model.set_option(option, value) def _on_test_button__clicked(self, button): self._print_test_bill()
def _update_bank_type(self): self._remove_bank_option_widgets() bank_number = self.bank_type.get_selected() bank_info = None if bank_number: bank_info = get_bank_info_by_number(bank_number) self.bank_number = ProxyEntry() self.bank_number.props.data_type = int self.bank_number.set_sensitive(False) bank_number_lbl = self._add_widget(api.escape(_("Number:")), self.bank_number, options=True) self.bank_branch = ProxyEntry() self.bank_branch.connect('validate', self._on_bank_branch__validate, bank_info) self.bank_branch.props.data_type = 'str' self.bank_branch.props.mandatory = True self.bank_branch.model_attribute = "bank_branch" bank_branch_lbl = self._add_widget(api.escape(_("Agency:")), self.bank_branch, options=True) if bank_number is not None: bank_branch_lbl.show() self.bank_branch.show() else: bank_branch_lbl.hide() self.bank_account = ProxyEntry() self.bank_account.connect('validate', self._on_bank_account__validate, bank_info) self._add_widget(api.escape(_("Account:")), self.bank_account, options=True) self.bank_account.model_attribute = "bank_account" self.bank_account.props.data_type = 'str' if bank_number is not None: self.bank_account.props.mandatory = True self.bank_account.show() attributes = ['bank_account', 'bank_branch', 'bank_number'] if bank_number is not None: bank_number_lbl.show() self.bank_number.show() self.bank_model.bank_number = bank_number for i, option in enumerate(bank_info.get_extra_options()): name = 'option' + str(i) entry = ProxyEntry() entry.model_attribute = name setattr(self, name, entry) # Set the model attr too so it can be validated setattr(self.bank_model, name, u'') entry.props.data_type = 'str' entry.connect('validate', self._on_bank_option__validate, bank_info, option) self._add_widget("<i>%s</i>:" % (api.escape(option.capitalize()), ), entry, options=True) entry.show() self._option_fields[option] = entry attributes.append(entry.model_attribute) else: bank_number_lbl.hide() self.bank_proxy = self.add_proxy( self.bank_model, attributes) self._fill_bank_account()
class DateEntry(Gtk.Box): """I am an entry which you can input a date on. In addition to an Gtk.Entry I also contain a button with an arrow you can click to get popup window with a Gtk.Calendar for which you can use to select the date """ gsignal('changed') gsignal('activate') def __init__(self): super(DateEntry, self).__init__(orientation=Gtk.Orientation.HORIZONTAL) self.get_style_context().add_class(Gtk.STYLE_CLASS_LINKED) self._popping_down = False self._old_date = None self._block_changed = False # This will force both the entry and the button have the same height self._sizegroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.VERTICAL) # bootstrap problems, kiwi.ui.widgets.entry imports dateentry # we need to use a proxy entry because we want the mask from kiwi.ui.widgets.entry import ProxyEntry self.entry = ProxyEntry() # Set datatype before connecting to change event, to not get when the # mask is set self.entry.set_property('data-type', datetime.date) self.entry.connect('changed', self._on_entry__changed) self.entry.connect('activate', self._on_entry__activate) mask = self.entry.get_mask() if mask: self.entry.set_width_chars(len(mask)) self.pack_start(self.entry, True, True, 0) self.entry.set_valign(Gtk.Align.CENTER) self._sizegroup.add_widget(self.entry) self.entry.show() self._button = Gtk.ToggleButton() self._button.set_valign(Gtk.Align.CENTER) self._button.connect('scroll-event', self._on_entry__scroll_event) self._button.connect('toggled', self._on_button__toggled) self._button.set_focus_on_click(False) self.pack_start(self._button, False, False, 0) self._sizegroup.add_widget(self._button) self._button.show() arrow = Gtk.Arrow(arrow_type=Gtk.ArrowType.DOWN, shadow_type=Gtk.ShadowType.NONE) self._button.add(arrow) arrow.show() self._popup = _DateEntryPopup(self) self._popup.connect('date-selected', self._on_popup__date_selected) self._popup.connect('hide', self._on_popup__hide) self._popup.set_size_request(-1, 24) self.set_valign(Gtk.Align.CENTER) # Virtual methods def do_grab_focus(self): self.entry.grab_focus() # Callbacks def _on_entry__changed(self, entry): try: date = self.get_date() except ValidationError: date = None self._changed(date) def _on_entry__activate(self, entry): self.emit('activate') def _on_entry__scroll_event(self, entry, event): if event.direction == Gdk.ScrollDirection.UP: days = 1 elif event.direction == Gdk.ScrollDirection.DOWN: days = -1 else: return try: date = self.get_date() except ValidationError: date = None if not date: newdate = datetime.date.today() else: newdate = date + datetime.timedelta(days=days) self.set_date(newdate) def _on_button__toggled(self, button): if self._popping_down: return try: date = self.get_date() except ValidationError: date = None self._popup.popup(date) def _on_popup__hide(self, popup): self._popping_down = True self._button.set_active(False) self._popping_down = False def _on_popup__date_selected(self, popup, date): self.set_date(date) popup.popdown() self.entry.grab_focus() self.entry.set_position(len(self.entry.get_text())) self._changed(date) def _changed(self, date): if self._block_changed: return if self._old_date != date: self.emit('changed') self._old_date = date # Public API def set_date(self, date): """Sets the date. :param date: date to set :type date: a datetime.date instance or None """ if not isinstance(date, datetime.date) and date not in [None, ValueUnset]: raise TypeError( "date must be a datetime.date instance or None, not %r" % ( date,)) if date in [None, ValueUnset]: value = '' else: value = date_converter.as_string(date) # We're block the changed call and doing it manually because # set_text() triggers a delete-text and then an insert-text, # both which are emitting an entry::changed signal self._block_changed = True self.entry.set_text(value) self._block_changed = False self._changed(date) def get_date(self): """Get the selected date :returns: the date. :rtype: datetime.date or None """ try: date = self.entry.read() except ValidationError: date = None if date == ValueUnset: date = None return date
class DateEntry(Gtk.Box): """I am an entry which you can input a date on. In addition to an Gtk.Entry I also contain a button with an arrow you can click to get popup window with a Gtk.Calendar for which you can use to select the date """ gsignal('changed') gsignal('activate') def __init__(self): super(DateEntry, self).__init__(orientation=Gtk.Orientation.HORIZONTAL) self._popping_down = False self._old_date = None self._block_changed = False # This will force both the entry and the button have the same height self._sizegroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.VERTICAL) # bootstrap problems, kiwi.ui.widgets.entry imports dateentry # we need to use a proxy entry because we want the mask from kiwi.ui.widgets.entry import ProxyEntry self.entry = ProxyEntry() # Set datatype before connecting to change event, to not get when the # mask is set self.entry.set_property('data-type', datetime.date) self.entry.connect('changed', self._on_entry__changed) self.entry.connect('activate', self._on_entry__activate) mask = self.entry.get_mask() if mask: self.entry.set_width_chars(len(mask)) self.pack_start(self.entry, True, True, 0) self.entry.set_valign(Gtk.Align.CENTER) self._sizegroup.add_widget(self.entry) self.entry.show() self._button = Gtk.ToggleButton() self._button.set_valign(Gtk.Align.CENTER) self._button.connect('scroll-event', self._on_entry__scroll_event) self._button.connect('toggled', self._on_button__toggled) self._button.set_focus_on_click(False) self.pack_start(self._button, False, False, 0) self._sizegroup.add_widget(self._button) self._button.show() arrow = Gtk.Arrow(Gtk.ArrowType.DOWN, Gtk.ShadowType.NONE) self._button.add(arrow) arrow.show() self._popup = _DateEntryPopup(self) self._popup.connect('date-selected', self._on_popup__date_selected) self._popup.connect('hide', self._on_popup__hide) self._popup.set_size_request(-1, 24) self.set_valign(Gtk.Align.CENTER) # Virtual methods def do_grab_focus(self): self.entry.grab_focus() # Callbacks def _on_entry__changed(self, entry): try: date = self.get_date() except ValidationError: date = None self._changed(date) def _on_entry__activate(self, entry): self.emit('activate') def _on_entry__scroll_event(self, entry, event): if event.direction == Gdk.ScrollDirection.UP: days = 1 elif event.direction == Gdk.ScrollDirection.DOWN: days = -1 else: return try: date = self.get_date() except ValidationError: date = None if not date: newdate = datetime.date.today() else: newdate = date + datetime.timedelta(days=days) self.set_date(newdate) def _on_button__toggled(self, button): if self._popping_down: return try: date = self.get_date() except ValidationError: date = None self._popup.popup(date) def _on_popup__hide(self, popup): self._popping_down = True self._button.set_active(False) self._popping_down = False def _on_popup__date_selected(self, popup, date): self.set_date(date) popup.popdown() self.entry.grab_focus() self.entry.set_position(len(self.entry.get_text())) self._changed(date) def _changed(self, date): if self._block_changed: return if self._old_date != date: self.emit('changed') self._old_date = date # Public API def set_date(self, date): """Sets the date. :param date: date to set :type date: a datetime.date instance or None """ if not isinstance(date, datetime.date) and date not in [ None, ValueUnset ]: raise TypeError( "date must be a datetime.date instance or None, not %r" % (date, )) if date in [None, ValueUnset]: value = '' else: value = date_converter.as_string(date) # We're block the changed call and doing it manually because # set_text() triggers a delete-text and then an insert-text, # both which are emitting an entry::changed signal self._block_changed = True self.entry.set_text(value) self._block_changed = False self._changed(date) def get_date(self): """Get the selected date :returns: the date. :rtype: datetime.date or None """ try: date = self.entry.read() except ValidationError: date = None if date == ValueUnset: date = None return date
class CalculatorPopup(gtk.Window): """A popup calculator for entries Right now it supports both :class:`kiwi.ui.widgets.spinbutton.ProxySpinButton` and :class:`kiwi.ui.widgets.entry.ProxyEntry`, as long as their data types are numeric (e.g. int, currency, Decimal, etc) """ #: The add mode. Any value typed on the entry will be added to the #: original value. e.g. 10% means +10% MODE_ADD = 0 #: The sub mode. Any value typed on the entry will be subtracted from the #: original value. e.g. 10% means -10% MODE_SUB = 1 _mode = None _data_type_mapper = { 'currency': currency, 'Decimal': decimal.Decimal, } def __init__(self, entry, mode): """ :param entry: a :class:`kiwi.ui.widgets.spinbutton.ProxySpinButton` or a :class:`kiwi.ui.widgets.entry.ProxyEntry` subclass :param mode: one of :attr:`.MODE_ADD`, :attr:`.MODE_SUB` """ self._mode = mode self._new_value = None self._popped_entry = entry self._data_type = self._data_type_mapper[entry.data_type] self._converter = converter.get_converter(self._data_type) gtk.Window.__init__(self, gtk.WINDOW_POPUP) self._create_ui() # # Public API # def popup(self): if not self._popped_entry.get_realized(): return if not self._update_ui(): return toplevel = self._popped_entry.get_toplevel().get_toplevel() if (isinstance(toplevel, (gtk.Window, gtk.Dialog)) and toplevel.get_group()): toplevel.get_group().add_window(self) # width is meant for the popup window x, y, width, height = self._get_position() self.set_size_request(width, -1) self.move(x, y) self.show_all() if not self._popup_grab_window(): self.hide() return self.grab_add() def popdown(self): if not self._popped_entry.get_realized(): return self.grab_remove() self.hide() self._popped_entry.grab_focus() # # Private # def _create_ui(self): self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.KEY_PRESS_MASK) self.connect('key-press-event', self._on__key_press_event) self.connect('button-press-event', self._on__button_press_event) # This is done on entry to check where to put the validation/mandatory # icons. We should put the calculator on the other side. # Note that spinbuttons are always right aligned and thus # xalign will always be 1.0 if self._popped_entry.get_property('xalign') > 0.5: self._icon_pos = 'secondary-icon' else: self._icon_pos = 'primary-icon' self._popped_entry.set_property(self._icon_pos + '-activatable', True) self._popped_entry.set_property(self._icon_pos + '-tooltip-text', _("Do calculations on top of this value")) self._popped_entry.connect('notify::sensitive', self._on_popped_entry_sensitive__notify) self._popped_entry.connect('icon-press', self._on_popped_entry__icon_press) self._toggle_calculator_icon() frame = gtk.Frame() frame.set_shadow_type(gtk.SHADOW_ETCHED_OUT) self.add(frame) frame.show() alignment = gtk.Alignment(0.5, 0.5, 1.0, 1.0) alignment.set_padding(6, 6, 2, 2) frame.add(alignment) alignment.show() vbox = gtk.VBox(spacing=6) alignment.add(vbox) vbox.show() self._main_label = gtk.Label() self._main_label.set_ellipsize(pango.ELLIPSIZE_END) vbox.pack_start(self._main_label, True, True) self._main_label.show() self._entry = ProxyEntry() # FIXME: We need a model_attribute here or else the entry.is_valid() # will always return None. Check proxywidget.py's FIXME to see why self._entry.model_attribute = 'not_used' self._entry.connect('validate', self._on_entry__validate) self._entry.connect_after('changed', self._after_entry__changed) self._entry.set_alignment(1.0) vbox.pack_start(self._entry, True, True) self._entry.show() hbox = gtk.HBox(spacing=6) vbox.pack_start(hbox, True, True) hbox.show() self._label = gtk.Label() self._label.set_property('xalign', 1.0) self._label.set_use_markup(True) hbox.pack_start(self._label, True, True) self._label.show() self._warning = gtk.Image() hbox.pack_start(self._warning, False, False) self.set_resizable(False) self.set_screen(self._popped_entry.get_screen()) def _update_ui(self): try: self._new_value = self._data_type(self._popped_entry.get_text()) except decimal.InvalidOperation: return False self._entry.set_text('') self._entry.set_tooltip_text(_("Use absolute or percentage (%) value")) self._preview_new_value() self._main_label.set_text(self._get_main_label()) return True def _get_main_label(self): if self._mode == self.MODE_ADD: return (_("Surcharge") if self._data_type == currency else _("Addition")) elif self._mode == self.MODE_SUB: return (_("Discount") if self._data_type == currency else _("Subtraction")) else: raise AssertionError def _set_warning(self, warning): if warning is None: self._warning.hide() else: self._warning.set_from_stock(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_MENU) self._warning.set_tooltip_text(warning) self._warning.show() def _popup_grab_window(self): activate_time = 0L window = self.get_window() grab_status = gtk.gdk.pointer_grab(window, True, (gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.POINTER_MOTION_MASK), None, None, activate_time) if grab_status == gtk.gdk.GRAB_SUCCESS: if gtk.gdk.keyboard_grab(window, True, activate_time) == 0: return True else: window.get_display().pointer_ungrab(activate_time) return False return False def _get_position(self): widget = self._popped_entry allocation = widget.get_allocation() window = widget.get_window() if hasattr(window, 'get_root_coords'): x = 0 y = 0 if not widget.get_has_window(): x += allocation.x y += allocation.y x, y = window.get_root_coords(x, y) else: # PyGTK lacks gdk_window_get_root_coords(), # but we can use get_origin() instead, which is the # same thing in our case. x, y = widget.window.get_origin() return x, y + allocation.height, allocation.width, allocation.height def _get_new_value(self): operation = self._entry.get_text().strip() operation = operation.replace(',', '.') if operation.endswith('%'): op_value = operation[:-1] percentage = True else: op_value = operation percentage = False if not operation: return if operation[0] in ['+', '-']: raise ValueError(_("Operator signals are not supported...")) if self._mode == self.MODE_SUB: op = operator.sub elif self._mode == self.MODE_ADD: op = operator.add try: op_value = decimal.Decimal(op_value) except decimal.InvalidOperation: raise ValueError( _("'%s' is not a valid operation...") % (operation,)) if percentage: value = op(self._new_value, self._new_value * (op_value / 100)) else: value = op(self._new_value, op_value) return value def _update_new_value(self): if not self._entry.is_valid(): return self._new_value = self._get_new_value() self._entry.set_text('') self._preview_new_value() def _preview_new_value(self): self._label.set_markup('<b>%s</b>' % ( self._converter.as_string(self._new_value), )) def _maybe_apply_new_value(self): if self._entry.get_text(): self._update_new_value() return self._popped_entry.update(self._new_value) self.popdown() def _toggle_calculator_icon(self): if self._popped_entry.get_sensitive(): pixbuf = self.render_icon(STOQ_CALC, gtk.ICON_SIZE_MENU) else: pixbuf = None self._popped_entry.set_property(self._icon_pos + '-pixbuf', pixbuf) # # Callbacks # def _on__key_press_event(self, window, event): keyval = event.keyval if keyval == gtk.keysyms.Escape: self.popdown() return True elif keyval in [gtk.keysyms.Return, gtk.keysyms.KP_Enter, gtk.keysyms.Tab]: self._maybe_apply_new_value() return True return False def _on__button_press_event(self, window, event): # If we're clicking outside of the window # close the popup if (event.window != self.get_window() or (tuple(self.allocation.intersect( gtk.gdk.Rectangle(x=int(event.x), y=int(event.y), width=1, height=1)))) == (0, 0, 0, 0)): self.popdown() def _on_entry__validate(self, entry, value): try: value = self._get_new_value() except ValueError as err: return ValidationError('%s\n%s' % (err, _("Use absolute or percentage (%) value"))) if value: warning = self._popped_entry.emit('validate', value) warning = warning and str(warning) else: warning = None self._set_warning(warning) def _after_entry__changed(self, entry): entry.validate(force=True) def _on_popped_entry_sensitive__notify(self, obj, pspec): self._toggle_calculator_icon() def _on_popped_entry__icon_press(self, entry, icon_pos, event): if icon_pos != gtk.ENTRY_ICON_SECONDARY: return self.popup()
class AccountEditor(BaseEditor): """ Account Editor """ gladefile = "AccountEditor" proxy_widgets = ['description', 'code'] model_type = Account model_name = _('Account') def __init__(self, store, model=None, parent_account=None): self._last_account_type = None self._bank_number = -1 self._bank_widgets = [] self._bank_option_widgets = [] self._option_fields = {} self._test_button = None self.existing = model is not None self.parent_account = parent_account self.bank_model = _TemporaryBankAccount() BaseEditor.__init__(self, store, model) action_area = self.main_dialog.action_area action_area.set_layout(gtk.BUTTONBOX_END) action_area.pack_start(self._test_button, expand=False, fill=False) action_area.set_child_secondary(self._test_button, True) self._test_button.show() # # BaseEditor hooks # def create_model(self, store): return Account(description=u"", account_type=Account.TYPE_CASH, store=store) def _setup_widgets(self): self._test_button = gtk.Button(_("Print a test bill")) self._test_button.connect('clicked', self._on_test_button__clicked) self.parent_accounts = AccountTree(with_code=False, create_mode=True) self.parent_accounts.connect('selection-changed', self._on_parent_accounts__selection_changed) self.tree_box.pack_start(self.parent_accounts) self.tree_box.reorder_child(self.parent_accounts, 0) if self.model == sysparam(self.store).IMBALANCE_ACCOUNT: self.account_type.set_sensitive(False) self.account_type.prefill(Account.account_type_descriptions) account_type = self.model.account_type self.parent_accounts.insert_initial(self.store, edited_account=self.model) if self.parent_account: account = self.parent_accounts.get_account_by_id( self.parent_account.id) self.parent_accounts.select(account) if not self.existing: account_type = account.account_type self.account_type.select(account_type) self.parent_accounts.show() def setup_proxies(self): self._setup_widgets() self.add_proxy(self.model, AccountEditor.proxy_widgets) def validate_confirm(self): if not self.model.description: return False account = self.parent_accounts.get_selected() if not account: return True return account.selectable def on_confirm(self): new_parent = self.parent_accounts.get_selected() if new_parent: new_parent = new_parent.account if new_parent != self.model: self.model.parent = new_parent self.model.account_type = self.account_type.get_selected() self._save_bank() def refresh_ok(self, value): BaseEditor.refresh_ok(self, value) account_type = self.account_type.get_selected() if account_type != Account.TYPE_BANK: value = False self._test_button.set_sensitive(value) # Private def _save_bank(self): bank_account = self.model.bank if not bank_account: bank_account = BankAccount(account=self.model, store=self.store) # FIXME: Who sets this to a str? bank_account.bank_account = unicode(self.bank_model.bank_account) bank_account.bank_branch = unicode(self.bank_model.bank_branch) if self._bank_number is not None: bank_account.bank_number = self.bank_model.bank_number self._save_bank_bill_options(bank_account) def _save_bank_bill_options(self, bank_account): for option, entry in self._option_fields.items(): value = unicode(entry.get_text()) bill_option = self.store.find(BillOption, bank_account=bank_account, option=option).one() if bill_option is None: bill_option = BillOption(store=self.store, bank_account=bank_account, option=option, value=value) bill_option.value = value def _add_widget(self, label, widget, options=False): n_rows = self.table.props.n_rows l = gtk.Label() l.set_markup(label) l.props.xalign = 1.0 self.table.resize(n_rows + 1, 2) self.table.attach( l, 0, 1, n_rows, n_rows + 1, gtk.FILL, 0, 0, 0) self.table.attach( widget, 1, 2, n_rows, n_rows + 1, gtk.EXPAND | gtk.FILL, 0, 0, 0) if options: self._bank_option_widgets.extend([l, widget]) else: self._bank_widgets.extend([l, widget]) l.show() return l def _update_bank_type(self): self._remove_bank_option_widgets() bank_number = self.bank_type.get_selected() bank_info = None if bank_number: bank_info = get_bank_info_by_number(bank_number) self.bank_number = ProxyEntry() self.bank_number.props.data_type = int self.bank_number.set_sensitive(False) bank_number_lbl = self._add_widget(api.escape(_("Number:")), self.bank_number, options=True) self.bank_branch = ProxyEntry() self.bank_branch.connect('validate', self._on_bank_branch__validate, bank_info) self.bank_branch.props.data_type = 'str' self.bank_branch.props.mandatory = True self.bank_branch.model_attribute = "bank_branch" bank_branch_lbl = self._add_widget(api.escape(_("Agency:")), self.bank_branch, options=True) if bank_number is not None: bank_branch_lbl.show() self.bank_branch.show() else: bank_branch_lbl.hide() self.bank_account = ProxyEntry() self.bank_account.connect('validate', self._on_bank_account__validate, bank_info) self._add_widget(api.escape(_("Account:")), self.bank_account, options=True) self.bank_account.model_attribute = "bank_account" self.bank_account.props.data_type = 'str' if bank_number is not None: self.bank_account.props.mandatory = True self.bank_account.show() attributes = ['bank_account', 'bank_branch', 'bank_number'] if bank_number is not None: bank_number_lbl.show() self.bank_number.show() self.bank_model.bank_number = bank_number for i, option in enumerate(bank_info.get_extra_options()): name = 'option' + str(i) entry = ProxyEntry() entry.model_attribute = name setattr(self, name, entry) # Set the model attr too so it can be validated setattr(self.bank_model, name, u'') entry.props.data_type = 'str' entry.connect('validate', self._on_bank_option__validate, bank_info, option) self._add_widget("<i>%s</i>:" % (api.escape(option.capitalize()), ), entry, options=True) entry.show() self._option_fields[option] = entry attributes.append(entry.model_attribute) else: bank_number_lbl.hide() self.bank_proxy = self.add_proxy( self.bank_model, attributes) self._fill_bank_account() def _fill_bank_account(self): if not self.model.bank: return self.bank_model.bank_branch = self.model.bank.bank_branch.encode('utf-8') self.bank_model.bank_account = self.model.bank.bank_account.encode('utf-8') self.bank_proxy.update('bank_branch') self.bank_proxy.update('bank_account') bill_options = list(self.store.find(BillOption, bank_account=self.model.bank)) for bill_option in bill_options: if bill_option.option is None: continue field_entry = self._option_fields.get(bill_option.option) if field_entry: field_entry.set_text(bill_option.value) def _update_account_type(self, account_type): if not self.account_type.get_sensitive(): return if account_type != Account.TYPE_BANK: self._remove_bank_widgets() self._remove_bank_option_widgets() self.code.set_sensitive(True) return self.code.set_sensitive(False) self.bank_type = ProxyComboBox() self._add_widget(api.escape(_("Bank:")), self.bank_type) self.bank_type.connect('content-changed', self._on_bank_type__content_changed) self.bank_type.show() banks = [(_('Generic'), None)] banks.extend([(b.description, b.bank_number) for b in get_all_banks()]) self.bank_type.prefill(banks) if self.model.bank: try: self.bank_type.select(self.model.bank.bank_number) except KeyError: self.bank_type.select(None) self._update_bank_type() def _remove_bank_widgets(self): for widget in self._bank_widgets: widget.get_parent().remove(widget) widget.destroy() self.table.resize(5, 2) self._bank_widgets = [] def _remove_bank_option_widgets(self): for widget in self._bank_option_widgets: widget.get_parent().remove(widget) widget.destroy() self.table.resize(5 + len(self._bank_widgets) / 2, 2) self._bank_option_widgets = [] self._option_fields = {} def _print_test_bill(self): try: bank_info = get_bank_info_by_number(self.bank_model.bank_number) except NotImplementedError: info(_("This bank does not support printing of bills")) return kwargs = dict( valor_documento=12345.67, data_vencimento=datetime.date.today(), data_documento=datetime.date.today(), data_processamento=datetime.date.today(), nosso_numero=u'624533', numero_documento=u'1138', sacado=[_(u"Drawee"), _(u"Address"), _(u"Details")], cedente=_(u"Supplier"), demonstrativo=[_(u"Demonstration")], instrucoes=[_(u"Instructions")], agencia=self.bank_model.bank_branch, conta=self.bank_model.bank_account, ) for opt in self.bank_model.options: kwargs[opt.option] = opt.value data = bank_info(**kwargs) print_report(BillTestReport, data) # Callbacks def _on_parent_accounts__selection_changed(self, objectlist, account): self.force_validation() def on_description__activate(self, entry): if self.validate_confirm(): self.confirm() def on_description__validate(self, entry, text): if not text: return ValidationError(_("Account description cannot be empty")) def on_account_type__content_changed(self, account_type): account_type = account_type.get_selected() if self._last_account_type == account_type: return self._update_account_type(account_type) self._last_account_type = account_type def _on_bank_type__content_changed(self, bank_type): bank_number = bank_type.get_selected() if self._bank_number == bank_number: return self._update_bank_type() self._bank_number = bank_number def _on_bank_branch__validate(self, entry, value, bank_info): if bank_info: try: bank_info.validate_field(value) except BoletoException, e: return ValidationError(str(e))
class DateEntry(gtk.HBox): """I am an entry which you can input a date on. In addition to an gtk.Entry I also contain a button with an arrow you can click to get popup window with a gtk.Calendar for which you can use to select the date """ gsignal('changed') gsignal('activate') def __init__(self): gtk.HBox.__init__(self) self._popping_down = False self._old_date = None # bootstrap problems, kiwi.ui.widgets.entry imports dateentry # we need to use a proxy entry because we want the mask from kiwi.ui.widgets.entry import ProxyEntry self.entry = ProxyEntry() self.entry.connect('changed', self._on_entry__changed) self.entry.connect('activate', self._on_entry__activate) self.entry.set_property('data-type', datetime.date) mask = self.entry.get_mask() if mask: self.entry.set_width_chars(len(mask)) self.pack_start(self.entry, False, False) self.entry.show() self._button = gtk.ToggleButton() self._button.connect('scroll-event', self._on_entry__scroll_event) self._button.connect('toggled', self._on_button__toggled) self._button.set_focus_on_click(False) self.pack_start(self._button, False, False) self._button.show() arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_NONE) self._button.add(arrow) arrow.show() self._popup = _DateEntryPopup(self) self._popup.connect('date-selected', self._on_popup__date_selected) self._popup.connect('hide', self._on_popup__hide) self._popup.set_size_request(-1, 24) # Virtual methods def do_grab_focus(self): self.entry.grab_focus() # Callbacks def _on_entry__changed(self, entry): try: date = self.get_date() except ValidationError: date = None self._changed(date) def _on_entry__activate(self, entry): self.emit('activate') def _on_entry__scroll_event(self, entry, event): if event.direction == gdk.SCROLL_UP: days = 1 elif event.direction == gdk.SCROLL_DOWN: days = -1 else: return try: date = self.get_date() except ValidationError: date = None if not date: newdate = datetime.date.today() else: newdate = date + datetime.timedelta(days=days) self.set_date(newdate) def _on_button__toggled(self, button): if self._popping_down: return try: date = self.get_date() except ValidationError: date = None self._popup.popup(date) def _on_popup__hide(self, popup): self._popping_down = True self._button.set_active(False) self._popping_down = False def _on_popup__date_selected(self, popup, date): self.set_date(date) popup.popdown() self.entry.grab_focus() self.entry.set_position(len(self.entry.get_text())) self._changed(date) def _changed(self, date): if self._old_date != date: self.emit('changed') self._old_date = date # Public API def set_date(self, date): """Sets the date. @param date: date to set @type date: a datetime.date instance or None """ if not isinstance(date, datetime.date) and date is not None: raise TypeError( "date must be a datetime.date instance or None, not %r" % (date, )) if date is None: value = '' else: value = date_converter.as_string(date) self.entry.set_text(value) def get_date(self): """Get the selected date @returns: the date. @rtype: datetime.date or None """ try: date = self.entry.read() except ValidationError: date = None if date == ValueUnset: date = None return date
def _update_bank_type(self): self._remove_bank_option_widgets() bank_number = self.bank_type.get_selected() bank_info = None if bank_number: bank_info = get_bank_info_by_number(bank_number) self.bank_number = ProxyEntry() self.bank_number.props.data_type = int self.bank_number.set_sensitive(False) bank_number_lbl = self._add_widget(api.escape(_("Number:")), self.bank_number, options=True) self.bank_branch = ProxyEntry() self.bank_branch.connect('validate', self._on_bank_branch__validate, bank_info) self.bank_branch.props.data_type = 'str' self.bank_branch.props.mandatory = True self.bank_branch.model_attribute = "bank_branch" bank_branch_lbl = self._add_widget(api.escape(_("Agency:")), self.bank_branch, options=True) if bank_number is not None: bank_branch_lbl.show() self.bank_branch.show() else: bank_branch_lbl.hide() self.bank_account = ProxyEntry() self.bank_account.connect('validate', self._on_bank_account__validate, bank_info) self._add_widget(api.escape(_("Account:")), self.bank_account, options=True) self.bank_account.model_attribute = "bank_account" self.bank_account.props.data_type = 'str' if bank_number is not None: self.bank_account.props.mandatory = True self.bank_account.show() attributes = ['bank_account', 'bank_branch', 'bank_number'] if bank_number is not None: bank_number_lbl.show() self.bank_number.show() self.bank_model.bank_number = bank_number for i, option in enumerate(bank_info.get_extra_options()): name = 'option' + str(i) entry = ProxyEntry() entry.model_attribute = name setattr(self, name, entry) # Set the model attr too so it can be validated setattr(self.bank_model, name, u'') entry.props.data_type = 'str' entry.connect('validate', self._on_bank_option__validate, bank_info, option) name = option.replace('_', ' ').capitalize() self._add_widget("<i>%s</i>:" % api.escape(name), entry, options=True) entry.show() self._option_fields[option] = entry attributes.append(entry.model_attribute) else: bank_number_lbl.hide() self.bank_proxy = self.add_proxy(self.bank_model, attributes) self._fill_bank_account()
class CalculatorPopup(gtk.Window): """A popup calculator for entries Right now it supports both :class:`kiwi.ui.widgets.spinbutton.ProxySpinButton` and :class:`kiwi.ui.widgets.entry.ProxyEntry`, as long as their data types are numeric (e.g. int, currency, Decimal, etc) """ #: The add mode. Any value typed on the entry will be added to the #: original value. e.g. 10% means +10% MODE_ADD = 0 #: The sub mode. Any value typed on the entry will be subtracted from the #: original value. e.g. 10% means -10% MODE_SUB = 1 _mode = None _data_type_mapper = { 'currency': currency, 'Decimal': decimal.Decimal, } def __init__(self, entry, mode): """ :param entry: a :class:`kiwi.ui.widgets.spinbutton.ProxySpinButton` or a :class:`kiwi.ui.widgets.entry.ProxyEntry` subclass :param mode: one of :attr:`.MODE_ADD`, :attr:`.MODE_SUB` """ self._mode = mode self._new_value = None self._popped_entry = entry self._data_type = self._data_type_mapper[entry.data_type] self._converter = converter.get_converter(self._data_type) gtk.Window.__init__(self, gtk.WINDOW_POPUP) self._create_ui() # # Public API # def popup(self): if not self._popped_entry.get_realized(): return toplevel = self._popped_entry.get_toplevel().get_toplevel() if (isinstance(toplevel, (gtk.Window, gtk.Dialog)) and toplevel.get_group()): toplevel.get_group().add_window(self) # width is meant for the popup window x, y, width, height = self._get_position() self.set_size_request(width, -1) self.move(x, y) self.show_all() if not self._popup_grab_window(): self.hide() return self.grab_add() self._update_ui() def popdown(self): if not self._popped_entry.get_realized(): return self.grab_remove() self.hide() self._popped_entry.grab_focus() # # Private # def _create_ui(self): self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.KEY_PRESS_MASK) self.connect('key-press-event', self._on__key_press_event) self.connect('button-press-event', self._on__button_press_event) # This is done on entry to check where to put the validation/mandatory # icons. We should put the calculator on the other side. # Note that spinbuttons are always right aligned and thus # xalign will always be 1.0 if self._popped_entry.get_property('xalign') > 0.5: self._icon_pos = 'secondary-icon' else: self._icon_pos = 'primary-icon' self._popped_entry.set_property(self._icon_pos + '-activatable', True) self._popped_entry.set_property(self._icon_pos + '-tooltip-text', _("Do calculations on top of this value")) self._popped_entry.connect('notify::sensitive', self._on_popped_entry_sensitive__notify) self._popped_entry.connect('icon-press', self._on_popped_entry__icon_press) self._toggle_calculator_icon() frame = gtk.Frame() frame.set_shadow_type(gtk.SHADOW_ETCHED_OUT) self.add(frame) frame.show() alignment = gtk.Alignment(0.5, 0.5, 1.0, 1.0) alignment.set_padding(6, 6, 2, 2) frame.add(alignment) alignment.show() vbox = gtk.VBox(spacing=6) alignment.add(vbox) vbox.show() self._main_label = gtk.Label() self._main_label.set_ellipsize(pango.ELLIPSIZE_END) vbox.pack_start(self._main_label, True, True) self._main_label.show() self._entry = ProxyEntry() # FIXME: We need a model_attribute here or else the entry.is_valid() # will always return None. Check proxywidget.py's FIXME to see why self._entry.model_attribute = 'not_used' self._entry.connect('validate', self._on_entry__validate) self._entry.connect_after('changed', self._after_entry__changed) self._entry.set_alignment(1.0) vbox.pack_start(self._entry, True, True) self._entry.show() hbox = gtk.HBox(spacing=6) vbox.pack_start(hbox, True, True) hbox.show() self._label = gtk.Label() self._label.set_property('xalign', 1.0) self._label.set_use_markup(True) hbox.pack_start(self._label, True, True) self._label.show() self._warning = gtk.Image() hbox.pack_start(self._warning, False, False) self.set_resizable(False) self.set_screen(self._popped_entry.get_screen()) def _update_ui(self): self._new_value = self._data_type(self._popped_entry.get_text()) self._entry.set_text('') self._entry.set_tooltip_text(_("Use absolute or percentage (%) value")) self._preview_new_value() self._main_label.set_text(self._get_main_label()) def _get_main_label(self): if self._mode == self.MODE_ADD: return (_("Surcharge") if self._data_type == currency else _("Addition")) elif self._mode == self.MODE_SUB: return (_("Discount") if self._data_type == currency else _("Subtraction")) else: raise AssertionError def _set_warning(self, warning): if warning is None: self._warning.hide() else: self._warning.set_from_stock(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_MENU) self._warning.set_tooltip_text(warning) self._warning.show() def _popup_grab_window(self): activate_time = 0L window = self.get_window() grab_status = gtk.gdk.pointer_grab(window, True, (gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.POINTER_MOTION_MASK), None, None, activate_time) if grab_status == gtk.gdk.GRAB_SUCCESS: if gtk.gdk.keyboard_grab(window, True, activate_time) == 0: return True else: window.get_display().pointer_ungrab(activate_time) return False return False def _get_position(self): widget = self._popped_entry allocation = widget.get_allocation() window = widget.get_window() if hasattr(window, 'get_root_coords'): x = 0 y = 0 if not widget.get_has_window(): x += allocation.x y += allocation.y x, y = window.get_root_coords(x, y) else: # PyGTK lacks gdk_window_get_root_coords(), # but we can use get_origin() instead, which is the # same thing in our case. x, y = widget.window.get_origin() return x, y + allocation.height, allocation.width, allocation.height def _get_new_value(self): operation = self._entry.get_text().strip() operation = operation.replace(',', '.') if operation.endswith('%'): op_value = operation[:-1] percentage = True else: op_value = operation percentage = False if not operation: return if operation[0] in ['+', '-']: raise ValueError(_("Operator signals are not supported...")) if self._mode == self.MODE_SUB: op = operator.sub elif self._mode == self.MODE_ADD: op = operator.add try: op_value = decimal.Decimal(op_value) except decimal.InvalidOperation: raise ValueError( _("'%s' is not a valid operation...") % (operation,)) if percentage: value = op(self._new_value, self._new_value * (op_value / 100)) else: value = op(self._new_value, op_value) return value def _update_new_value(self): if not self._entry.is_valid(): return self._new_value = self._get_new_value() self._entry.set_text('') self._preview_new_value() def _preview_new_value(self): self._label.set_markup('<b>%s</b>' % ( self._converter.as_string(self._new_value), )) def _maybe_apply_new_value(self): if self._entry.get_text(): self._update_new_value() return self._popped_entry.update(self._new_value) self.popdown() def _toggle_calculator_icon(self): if self._popped_entry.get_sensitive(): pixbuf = self.render_icon(STOQ_CALC, gtk.ICON_SIZE_MENU) else: pixbuf = None self._popped_entry.set_property(self._icon_pos + '-pixbuf', pixbuf) # # Callbacks # def _on__key_press_event(self, window, event): keyval = event.keyval if keyval == gtk.keysyms.Escape: self.popdown() return True elif keyval in [gtk.keysyms.Return, gtk.keysyms.KP_Enter, gtk.keysyms.Tab]: self._maybe_apply_new_value() return True return False def _on__button_press_event(self, window, event): # If we're clicking outside of the window # close the popup if (event.window != self.get_window() or (tuple(self.allocation.intersect( gtk.gdk.Rectangle(x=int(event.x), y=int(event.y), width=1, height=1)))) == (0, 0, 0, 0)): self.popdown() def _on_entry__validate(self, entry, value): try: value = self._get_new_value() except ValueError as err: return ValidationError('%s\n%s' % (err, _("Use absolute or percentage (%) value"))) if value: warning = self._popped_entry.emit('validate', value) warning = warning and str(warning) else: warning = None self._set_warning(warning) def _after_entry__changed(self, entry): entry.validate(force=True) def _on_popped_entry_sensitive__notify(self, obj, pspec): self._toggle_calculator_icon() def _on_popped_entry__icon_press(self, entry, icon_pos, event): if icon_pos != gtk.ENTRY_ICON_SECONDARY: return self.popup()