Esempio n. 1
0
    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 _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
Esempio n. 3
0
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()
Esempio n. 4
0
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()
Esempio n. 5
0
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()
Esempio n. 6
0
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()