Beispiel #1
0
class ProxyVScale(Gtk.VScale, ProxyWidgetMixin):
    __gtype_name__ = 'ProxyVScale'
    allowed_data_types = (float, )
    data_type = GObject.Property(getter=ProxyWidgetMixin.get_data_type,
                                 setter=ProxyWidgetMixin.set_data_type,
                                 type=str,
                                 blurb='Data Type')
    model_attribute = GObject.Property(type=str, blurb='Model attribute')
    gsignal('content-changed')
    gsignal('validation-changed', bool)
    gsignal('validate', object, retval=object)

    def __init__(self, adjustment=None):
        if not adjustment:
            adjustment = Gtk.Adjustment(lower=-sys.maxsize,
                                        upper=sys.maxsize,
                                        value=0)
        Gtk.VScale.__init__(self, adjustment=adjustment)
        ProxyWidgetMixin.__init__(self)

        self.props.data_type = float
        # self.connect_after('value-changed', self._on_value_changed)

    def read(self):
        return self.get_value()

    def update(self, data):
        if data is None or data is ValueUnset:
            self.set_value(0.)
        else:
            self.set_value(data)

    def do_value_changed(self):
        self.emit('content-changed')
Beispiel #2
0
class SearchResultTreeView(ObjectTree):

    __gtype_name__ = 'SearchResultTreeView'

    gsignal("item-activated", object)
    gsignal("item-popup-menu", object, object)

    def __init__(self):
        ObjectTree.__init__(self)
        self.connect('double-click', self._on__double_click)
        self.connect('row-activated', self._on__row_activated)
        self.connect('right-click', self._on__right_click)

    def _add_result(self, result):
        parent = result.get_parent()
        if parent:
            self._add_result(parent)
        if not result in self:
            self.append(parent, result)
            if parent:
                self.expand(parent)

    #
    # ISearchResultView
    #

    def attach(self, search, columns):
        self._search = search
        self.set_columns(columns)

    def enable_lazy_search(self):
        pass

    def get_n_items(self):
        return len(self.get_model())

    def search_completed(self, results):
        for result in results:
            self._add_result(result)

    def get_settings(self):
        d = {}
        _serialize_columns(self.get_treeview(), d)
        return d

    def get_selected_item(self):
        return self.get_selected()

    #
    # Callbacks
    #

    def _on__double_click(self, object_list, item):
        self.emit('item-activated', item)

    def _on__row_activated(self, object_list, item):
        self.emit('item-activated', item)

    def _on__right_click(self, object_list, results, event):
        self.emit('item-popup-menu', results, event)
Beispiel #3
0
class ProxyColorButton(gtk.ColorButton, ProxyWidgetMixin):
    __gtype_name__ = 'ProxyColorButton'

    data_type = gobject.property(getter=ProxyWidgetMixin.get_data_type,
                                 setter=ProxyWidgetMixin.set_data_type,
                                 type=str,
                                 blurb='Data Type')
    model_attribute = gobject.property(type=str, blurb='Model attribute')
    gsignal('content-changed')
    gsignal('validation-changed', bool)
    gsignal('validate', object, retval=object)

    allowed_data_types = (basestring, )

    def __init__(self, color=gtk.gdk.Color(0, 0, 0)):
        ProxyWidgetMixin.__init__(self)
        gtk.ColorButton.__init__(self, color)

    gsignal('color-set', 'override')

    def do_color_set(self):
        self.emit('content-changed')
        self.chain()

    def read(self):
        color = self.get_color()
        return self._from_string(
            '#%02x%02x%02x' %
            (color.red / 256, color.green / 256, color.blue / 256))

    def update(self, data):
        if data is ValueUnset or data is None:
            data = 'black'
        color = gtk.gdk.color_parse(data)
        self.set_color(color)
Beispiel #4
0
class ProxyRadioButton(gtk.RadioButton, ProxyWidgetMixin):
    __gtype_name__ = 'ProxyRadioButton'
    allowed_data_types = object,
    data_value = gobject.property(type=str, nick='Data Value')
    data_type = gobject.property(getter=ProxyWidgetMixin.get_data_type,
                                 setter=ProxyWidgetMixin.set_data_type,
                                 type=str,
                                 blurb='Data Type')
    model_attribute = gobject.property(type=str, blurb='Model attribute')
    gsignal('content-changed')
    gsignal('validation-changed', bool)
    gsignal('validate', object, retval=object)

    def __init__(self, group=None, label=None, use_underline=True):
        gtk.RadioButton.__init__(self, None, label, use_underline)
        if group:
            self.set_group(group)
        ProxyWidgetMixin.__init__(self)
        self.connect('group-changed', self._on_group_changed)

    def _on_radio__toggled(self, radio):
        self.emit('content-changed')

    def _on_group_changed(self, radio):
        for radio in radio.get_group():
            radio.connect('toggled', self._on_radio__toggled)

    def get_selected(self):
        """
        Get the currently selected radiobutton.

        :returns: The selected :class:`RadioButton` or None if there are no
          selected radiobuttons.
        """

        for button in self.get_group():
            if button.get_active():
                return button

    def read(self):
        button = self.get_selected()
        if button is None:
            return ValueUnset

        return self._from_string(button.data_value)

    def update(self, data):
        if data is None or data is ValueUnset:
            # In a group of radiobuttons, the only widget which is in
            # the proxy is ourself, the other buttons do not get their
            # update() method called, so the default value is activate
            # ourselves when the model is empty
            self.set_active(True)
            return

        data = self._as_string(data)
        for rb in self.get_group():
            if rb.get_property('data-value') == data:
                rb.set_active(True)
Beispiel #5
0
class ProxyTextView(gtk.TextView, ValidatableProxyWidgetMixin):
    __gtype_name__ = 'ProxyTextView'
    data_value = gobject.property(type=str, nick='Data Value')
    data_type = gobject.property(
        getter=ValidatableProxyWidgetMixin.get_data_type,
        setter=ValidatableProxyWidgetMixin.set_data_type,
        type=str,
        blurb='Data Type')
    mandatory = gobject.property(type=bool, default=False)
    model_attribute = gobject.property(type=str, blurb='Model attribute')
    gsignal('content-changed')
    gsignal('validation-changed', bool)
    gsignal('validate', object, retval=object)
    allowed_data_types = (basestring, datetime.date) + number

    def __init__(self):
        self._is_unset = True
        gtk.TextView.__init__(self)
        self.props.data_type = str
        ValidatableProxyWidgetMixin.__init__(self)

        self._textbuffer = gtk.TextBuffer()
        self._textbuffer.connect('changed', self._on_textbuffer__changed)
        self.set_buffer(self._textbuffer)

    def _on_textbuffer__changed(self, textbuffer):
        self._is_unset = False
        self.emit('content-changed')
        self.read()

    def read(self):
        if self._is_unset:
            return ValueUnset
        textbuffer = self._textbuffer
        data = textbuffer.get_text(textbuffer.get_start_iter(),
                                   textbuffer.get_end_iter())
        return self._from_string(data)

    def update(self, data):
        if data is ValueUnset:
            self._textbuffer.set_text("")
            self._is_unset = True
            return
        elif data is None:
            text = ""
        else:
            self.is_unset = False
            text = self._as_string(data)

        self._textbuffer.set_text(text)
Beispiel #6
0
class ProxyVScale(_ProxyScale, ProxyWidgetMixin, gtk.VScale):
    __gtype_name__ = 'ProxyVScale'
    data_type = gobject.property(
        getter=ProxyWidgetMixin.get_data_type,
        setter=ProxyWidgetMixin.set_data_type,
        type=str, blurb='Data Type')
    model_attribute = gobject.property(type=str, blurb='Model attribute')
    gsignal('content-changed')
    gsignal('validation-changed', bool)
    gsignal('validate', object, retval=object)

    def __init__(self):
        gtk.VScale.__init__(self)
        ProxyWidgetMixin.__init__(self)
        self.props.data_type = float
Beispiel #7
0
class ContextMenuItem(gtk.ImageMenuItem):
    gsignal('can-disable', retval=bool)

    def __init__(self, label, stock=None):
        """An menu item with an (option) icon.

        you can use this in three different ways:

        ContextMenuItem('foo')
        ContextMenuItem('foo', gtk.STOCK_PASTE)
        ContextMenuItem(gtk.STOCK_PASTE)

        The first will display an label 'foo'. The second will display the same label with the
        paste icon, and the last will display the paste icon with the default paste label
        """
        # For some reason I don't know, gtk.ImageMenuItem.__init__(...) does not work. It
        # complains I should use __gobject_init__ instead.
        self.__gobject_init__()

        if not stock:
            stock = label
            info = gtk.stock_lookup(label)
            if info:
                label = info[1]

        lbl = gtk.AccelLabel(label)
        lbl.set_alignment(0, 0.5)
        lbl.set_use_underline(True)
        lbl.set_use_markup(True)
        self.add(lbl)

        image = gtk.Image()
        image.set_from_stock(stock, gtk.ICON_SIZE_MENU)
        self.set_image(image)
Beispiel #8
0
class ResourceStatus(GObject.GObject):
    """The status of a given resource"""

    gsignal('status-changed', int, str)

    (STATUS_NA, STATUS_OK, STATUS_WARNING, STATUS_ERROR) = range(4)

    status_label = {
        STATUS_NA: _("N/A"),
        STATUS_OK: _("OK"),
        STATUS_WARNING: _("WARNING"),
        STATUS_ERROR: _("ERROR"),
    }

    name = None
    label = None
    priority = 0

    def __init__(self):
        super(ResourceStatus, self).__init__()

        assert self.name is not None
        self.status = self.STATUS_OK
        self.reason = None
        self.reason_long = None

    def __eq__(self, other):
        if type(self) != type(other):
            return False
        return self.name == other.name

    @property
    def status_str(self):
        return self.status_label[self.status]

    def refresh(self):
        """Refresh the resource status

        Subclasses should override this and update
        :obj:`.status` and :obj:`.reason`

        Note that this will not be running on the main thread,
        so be cautelous with non thread-safe operations.
        """
        raise NotImplementedError

    def get_actions(self):
        """Get the actions that can be run for this resource"""
        return []

    def refresh_and_notify(self):
        """Refresh the resource status and notify for changes"""
        old_status, old_reason = self.status, self.reason
        self.refresh()

        if (self.status, self.reason) != (old_status, old_reason):
            # This is running on another so schedule the emit in the main one
            schedule_in_main_thread(self.emit, 'status-changed', self.status,
                                    self.reason)
Beispiel #9
0
class _SearchDialogDetailsSlave(GladeSlaveDelegate):
    """ Slave for internal use of SearchEditor, offering an eventbox for a
    toolbar and managing the 'New' and 'Edit' buttons. """

    gladefile = 'SearchDialogDetailsSlave'

    gsignal('details')
    gsignal('print')

    #
    # Kiwi handlers
    #

    def on_details_button__clicked(self, button):
        self.emit('details')

    def on_print_button__clicked(self, button):
        self.emit('print')
Beispiel #10
0
class ReportSubmitter(GObject.GObject):
    gsignal('failed', object)
    gsignal('submitted', object)

    def __init__(self):
        GObject.GObject.__init__(self)

        self._count = 0
        self._api = WebService()
        self.report = collect_report()

    def _done(self, args):
        self.emit('submitted', args)

    def _error(self, args):
        self.emit('failed', args)

    def submit(self):
        return self._api.bug_report(self.report,
                                    callback=self._on_report__callback,
                                    errback=self._on_report__errback)

    def _on_report__callback(self, response):
        if response.status_code == 200:
            self._on_success(response.json())
        else:
            self._on_error()

    def _on_report__errback(self, failure):
        self._on_error(failure)

    def _on_error(self, data=None):
        log.info('Failed to report bug: %r count=%d' % (data, self._count))
        if self._count < _N_TRIES:
            self.submit()
        else:
            schedule_in_main_thread(self.emit, 'failed', data)
        self._count += 1

    def _on_success(self, data):
        log.info('Finished sending bugreport: %r' % (data, ))
        schedule_in_main_thread(self.emit, 'submitted', data)
Beispiel #11
0
class SearchEditorToolBar(GladeSlaveDelegate):
    """ Slave for internal use of SearchEditor, offering an eventbox for a
    toolbar and managing the 'New' and 'Edit' buttons. """

    toplevel_name = 'ToolBar'
    gladefile = 'SearchEditor'
    domain = 'stoqlib'

    gsignal('edit')
    gsignal('add')

    #
    # Kiwi handlers
    #

    def on_edit_button__clicked(self, button):
        self.emit('edit')

    def on_new_button__clicked(self, button):
        self.emit('add')
Beispiel #12
0
class ProxyMultiCombo(MultiCombo, ProxyWidgetMixin):

    __gtype_name__ = 'ProxyMultiCombo'

    data_type = gobject.property(
        getter=ProxyWidgetMixin.get_data_type,
        setter=ProxyWidgetMixin.set_data_type,
        type=str, blurb='Data Type')
    model_attribute = gobject.property(type=str, blurb='Model attribute')
    gsignal('content-changed')
    gsignal('validation-changed', bool)
    gsignal('validate', object, retval=object)

    def __init__(self, **kwargs):
        ProxyWidgetMixin.__init__(self)
        MultiCombo.__init__(self, **kwargs)

        self.connect('item-added', self._on_combo__item_added)
        self.connect('item-removed', self._on_combo__item_removed)

    #
    #  ProxyWidgetMixin
    #

    def read(self):
        return self.get_selection_data()

    def update(self, data):
        if data is ValueUnset or data is None:
            return
        self.add_selection_by_data(data)

    #
    #  Callbacks
    #

    def _on_combo__item_added(self, multicombo, item):
        self.emit('content-changed')

    def _on_combo__item_removed(self, multicombo, item):
        self.emit('content-changed')
Beispiel #13
0
class ProxyCheckButton(gtk.CheckButton, ProxyWidgetMixin):
    __gtype_name__ = 'ProxyCheckButton'

    data_type = gobject.property(getter=ProxyWidgetMixin.get_data_type,
                                 setter=ProxyWidgetMixin.set_data_type,
                                 type=str,
                                 blurb='Data Type')
    model_attribute = gobject.property(type=str, blurb='Model attribute')
    gsignal('content-changed')
    gsignal('validation-changed', bool)
    gsignal('validate', object, retval=object)

    # changed allowed data types because checkbuttons can only
    # accept bool values
    allowed_data_types = bool,

    def __init__(self, label=None, use_underline=True):
        gtk.CheckButton.__init__(self,
                                 label=label,
                                 use_underline=use_underline)
        ProxyWidgetMixin.__init__(self)

    def __post_init__(self):
        self.props.data_type = bool

    gsignal('toggled', 'override')

    def do_toggled(self):
        self.emit('content-changed')
        self.chain()

    def read(self):
        return self.get_active()

    def update(self, data):
        if data is None or data is ValueUnset:
            self.set_active(False)
            return

        # No conversion to string needed, we only accept bool
        self.set_active(data)
Beispiel #14
0
class ReportSubmitter(gobject.GObject):
    gsignal('failed', object)
    gsignal('submitted', object)

    def __init__(self):
        gobject.GObject.__init__(self)

        self._api = WebService()
        self._report = collect_report()
        self._count = 0

    def _done(self, args):
        self.emit('submitted', args)

    def _error(self, args):
        self.emit('failed', args)

    @property
    def report(self):
        return self._report

    def submit(self):
        response = self._api.bug_report(self._report)
        response.addCallback(self._on_report__callback)
        response.addErrback(self._on_report__errback)
        return response

    def _on_report__callback(self, data):
        log.info('Finished sending bugreport: %r' % (data, ))
        self._done(data)

    def _on_report__errback(self, failure):
        log.info('Failed to report bug: %r count=%d' % (failure, self._count))
        if self._count < _N_TRIES:
            self.submit()
        else:
            self._error(failure)
        self._count += 1
Beispiel #15
0
class SearchDialogButtonSlave(GladeSlaveDelegate):
    """ Slave for internal use of SearchEditor, offering an eventbox for a
    toolbar and managing buttons. """

    gladefile = 'SearchDialogButtonSlave'

    gsignal('click')

    #
    # Kiwi handlers
    #

    def on_button__clicked(self, button):
        self.emit('click')
Beispiel #16
0
class SearchDialogPrintSlave(GladeSlaveDelegate):
    """ Slave for internal use of SearchEditor, offering an eventbox for a
    toolbar and managing the 'print_price_button' buttons. """

    domain = 'stoq'
    gladefile = 'SearchDialogPrintSlave'

    gsignal('print')

    #
    # Kiwi handlers
    #

    def on_print_price_button__clicked(self, button):
        self.emit('print')
class DojoMonitor(gobject.GObject):
    (STATUS_PASS,
     STATUS_FAIL,
     STATUS_ERROR,
     STATUS_LOAD_ERROR) = range(4)

    gsignal('status-changed', int, object)

    def __init__(self, name):
        gobject.GObject.__init__(self)

        uri = gnomevfs.make_uri_from_shell_arg(name)
        gnomevfs.monitor_add(uri,
                             gnomevfs.MONITOR_FILE,
                             self._on_monitor)

        self.name = name

    def _load_module(self):
        try:
            self.module = imp.load_source('tests', self.name)
        except:
            self.traceback = traceback.format_exc()
            self.module = None

    def _on_monitor(self, dir, file, event):
        if event == gnomevfs.MONITOR_EVENT_CHANGED:
            self.run_tests()

    def run_tests(self):
        self._load_module()

        if self.module is None:
            self.emit('status-changed',
                      self.STATUS_LOAD_ERROR,
                      self.traceback)
            return

        loader = unittest.TestLoader()
        suit = loader.loadTestsFromTestCase(self.module.StubTests)
        results = unittest.TestResult()
        suit.run(results)

        if results.failures:
            self.emit('status-changed', self.STATUS_FAIL, results)
        else:
            self.emit('status-changed', self.STATUS_PASS, results)
Beispiel #18
0
class _FileChooserMixin(object):
    """Mixin to use common methods of the FileChooser interface"""

    allowed_data_types = basestring,

    gsignal('selection_changed', 'override')

    def do_selection_changed(self):
        self.emit('content-changed')
        self.chain()

    def read(self):
        return self.get_filename()

    def update(self, data):
        if data is None:
            return
        self.set_filename(data)
Beispiel #19
0
class _ProxyScale:
    # changed allowed data types because scales can only
    # accept float values
    allowed_data_types = float,

    gsignal('value_changed', 'override')

    def do_value_changed(self):
        self.emit('content-changed')
        self.chain()

    def read(self):
        return self.get_value()

    def update(self, data):
        if data is None or data is ValueUnset:
            self.set_value(0.)
        else:
            self.set_value(data)
Beispiel #20
0
class AppGrid(Gtk.FlowBox):
    __gtype_name__ = 'AppGrid'

    gsignal('app-selected', object)

    def __init__(self,
                 window,
                 apps,
                 size_group,
                 large_icons=False,
                 min_children=2,
                 max_children=5):
        self.window = window
        super(AppGrid, self).__init__()

        self.set_row_spacing(3)
        self.set_column_spacing(3)

        self.set_homogeneous(True)
        self.set_min_children_per_line(min_children)
        self.set_max_children_per_line(max_children)
        self.connect('child-activated', self._on_row_activated)

        for app in apps:
            entry = AppEntry(app, large_icons)
            size_group.add_widget(entry)
            self.add(entry)

    def update_selection(self):
        self.unselect_all()
        for entry in self.get_children():
            if entry.app.name == self.window.current_app.app_name:
                self.select_child(entry)
                break

    def _on_row_activated(self, listbox, row):
        self.emit('app-selected', row.app)

        cur = self.window.current_app
        if cur and cur.can_change_application():
            self.window.run_application(row.app.name, hide=True)
Beispiel #21
0
class ProxyFontButton(gtk.FontButton, ProxyWidgetMixin):
    __gtype_name__ = 'ProxyFontButton'

    allowed_data_types = basestring,

    def __init__(self, fontname=None):
        ProxyWidgetMixin.__init__(self)
        gtk.FontButton.__init__(self, fontname)
        self.props.data_type = str

    gsignal('font-set', 'override')

    def do_font_set(self):
        self.emit('content-changed')
        self.chain()

    def read(self):
        return self.get_font_name()

    def update(self, data):
        self.set_font_name(data)
Beispiel #22
0
class ContextMenuItem(gtk.ImageMenuItem):
    gsignal('can-disable', retval=bool)

    def __init__(self, label, stock=None):
        """An menu item with an (option) icon.

        you can use this in three different ways:

        ContextMenuItem('foo')
        ContextMenuItem('foo', gtk.STOCK_PASTE)
        ContextMenuItem(gtk.STOCK_PASTE)

        The first will display an label 'foo'. The second will display the same label with the
        paste icon, and the last will display the paste icon with the default paste label
        """
        gtk.ImageMenuItem.__init__(self)

        if not stock:
            stock = label
            info = gtk.stock_lookup(label)
            if info:
                try:
                    label = info.label
                # For PyGTk
                except AttributeError:
                    label = info[1]

        lbl = gtk.AccelLabel(label)
        lbl.set_alignment(0, 0.5)
        lbl.set_use_underline(True)
        lbl.set_use_markup(True)
        self.add(lbl)

        image = gtk.Image()
        image.set_from_stock(stock, gtk.ICON_SIZE_MENU)
        self.set_image(image)
Beispiel #23
0
class _QueryEntryPopup(PopupWindow):

    gsignal('item-selected', object, bool)
    gsignal('create-item')

    PROPAGATE_KEY_PRESS = True
    GRAB_WINDOW = False

    def __init__(self, entry_gadget, has_new_item=True):
        self._has_new_item = has_new_item
        self.loading = False
        self.entry_gadget = entry_gadget
        super(_QueryEntryPopup, self).__init__(entry_gadget.entry)

    #
    #  Public API
    #

    def set_loading(self, loading):
        if loading == self.loading:
            return

        self.loading = loading
        self._model.clear()

        if loading:
            self._treeview.insert_column(self._spinner_column, 0)
            self._model.append(
                (_LOADING_ITEM_MARKER, self.entry_gadget.LOADING_ITEMS_TEXT,
                 None, True, 0))
            GLib.timeout_add(100, self._pulse_spinner_col)
        else:
            self._treeview.remove_column(self._spinner_column)

    def add_items(self, items):
        self.set_loading(False)
        self._model.clear()

        for item in items:
            label, tooltip = self.entry_gadget.describe_item(item)
            self._model.append((item, label, tooltip, False, 0))

        if len(self._model):
            self._selection.select_path(self._model[0].path)

        if self._has_new_item:
            self._model.append(
                (_NEW_ITEM_MARKER, self.entry_gadget.NEW_ITEM_TEXT,
                 None, False, 0))
        elif not len(self._model):
            self._model.append(
                (_NO_ITENS_MARKER, self.entry_gadget.NO_ITEMS_FOUND_TEXT,
                 None, False, 0))

        GLib.idle_add(self._resize)

    def scroll(self, relative=None, absolute=None):
        model, titer = self._selection.get_selected()

        if titer is None:
            row_no = 0
        elif relative is not None:
            row_no = model[titer].path[0] + relative
        elif absolute is not None:
            row_no = absolute
        else:
            raise TypeError("needs relative or absolute")

        if row_no < 0:
            path = (0, )
        elif row_no >= len(model):
            path = (len(model) - 1, )
        else:
            path = (row_no, )

        titer = model[path].iter
        self._selection.select_iter(titer)
        self._treeview.scroll_to_cell(path, None, False, 0, 0)

    #
    #  EntryPopup
    #

    def confirm(self, fallback_to_search=False):
        self._activate_selected_item(fallback_to_search=fallback_to_search)

    def handle_key_press_event(self, event):
        keyval = event.keyval
        # By default the PopupWindow will call confirm for both Return and
        # KP_Enter, but also for Tab and Space. We want to fallback to search
        # in those specific cases
        if keyval in [Gdk.KEY_Return, Gdk.KEY_KP_Enter]:
            self.confirm(fallback_to_search=True)
            return True

        return super(_QueryEntryPopup, self).handle_key_press_event(event)

    def get_main_widget(self):
        vbox = Gtk.VBox()

        self._sw = Gtk.ScrolledWindow()
        self._sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.NEVER)
        vbox.pack_start(self._sw, True, True, 0)

        self._model = Gtk.ListStore(object, str, str, bool, int)
        self._treeview = Gtk.TreeView(self._model)
        self._treeview.connect('motion-notify-event',
                               self._on_treeview__motion_notify_event)
        self._treeview.connect('button-release-event',
                               self._on_treeview__button_release_event)
        self._treeview.add_events(
            Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.KEY_PRESS_MASK)

        self._treeview.set_tooltip_column(COL_TOOLTIP)
        self._treeview.set_enable_search(False)

        self._selection = self._treeview.get_selection()
        self._selection.set_mode(Gtk.SelectionMode.BROWSE)

        self._spinner_renderer = Gtk.CellRendererSpinner()
        self._spinner_column = Gtk.TreeViewColumn(
            '', self._spinner_renderer,
            active=COL_SPINNER_ACTIVE, pulse=COL_SPINNER_PULSE)

        self._renderer = ComboDetailsCellRenderer(use_markup=True)
        self._treeview.append_column(
            Gtk.TreeViewColumn('', self._renderer, label=COL_MARKUP))

        self._treeview.set_headers_visible(False)
        self._sw.add(self._treeview)

        vbox.show_all()
        return vbox

    def get_size(self, allocation, monitor):
        self._treeview.realize()
        width = allocation.width

        cells_height = sum(
            self._treeview.get_background_area(
                Gtk.TreePath(path), self._treeview.get_column(0)).height
            for path in range(len(self._treeview.get_model())))
        # Use half of the available screen space
        height = min(cells_height, monitor.height / 2)
        height += self.FRAME_PADDING[0] + self.FRAME_PADDING[1]

        hscroll = self._sw.get_hscrollbar()
        if hscroll is not None and hscroll.get_visible():
            hscroll_allocation = hscroll.get_allocation()
            height += hscroll_allocation.height

        return width, height

    def popup(self):
        self.set_loading(True)
        super(_QueryEntryPopup, self).popup()
        self._treeview.set_size_request(-1, -1)
        self.attached_widget.grab_focus()
        self.attached_widget.select_region(
            len(self.attached_widget.get_text()), -1)

    def popdown(self):
        super(_QueryEntryPopup, self).popdown()
        self.set_loading(False)

    #
    #  Private
    #

    def _resize(self):
        widget = self.get_widget_for_popup()
        allocation = widget.get_allocation()
        screen = widget.get_screen()
        window = widget.get_window()
        # FIXME: window will be None on a test, but it is hard to tell which
        # one since it breaks one randomly because of the idle_add.
        if window is not None:
            monitor_num = screen.get_monitor_at_window(widget.get_window())
        else:
            monitor_num = 0
        monitor = screen.get_monitor_geometry(monitor_num)

        self.set_size_request(*self.get_size(allocation, monitor))
        self._treeview.set_size_request(-1, -1)

    def _pulse_spinner_col(self):
        for item in self._model:
            if not item[COL_SPINNER_ACTIVE]:
                continue
            item[COL_SPINNER_PULSE] += 1
        return self.loading

    def _select_item(self, item, fallback_to_search=False):
        if item in [_LOADING_ITEM_MARKER, _NO_ITENS_MARKER]:
            pass
        elif item is _NEW_ITEM_MARKER:
            self.popdown()
            self.emit('create-item')
        else:
            self.emit('item-selected', item, fallback_to_search)

    def _select_path_for_event(self, event):
        path = self._treeview.get_path_at_pos(int(event.x), int(event.y))
        if not path:
            return

        path, column, x, y = path
        self._selection.select_path(path)
        self._treeview.set_cursor(path)

    def _activate_selected_item(self, fallback_to_search=False):
        model, treeiter = self._selection.get_selected()
        self._select_item(treeiter and model[treeiter][COL_ITEM],
                          fallback_to_search=fallback_to_search)

    #
    #  Callbacks
    #

    def _on_treeview__motion_notify_event(self, treeview, event):
        self._select_path_for_event(event)

    def _on_treeview__button_release_event(self, treeview, event):
        self._select_path_for_event(event)
        self._activate_selected_item()
Beispiel #24
0
class ProxyButton(Gtk.Button, ProxyWidgetMixin):
    """
    A ProxyButton is a Button subclass which is implementing the features
    required to be used inside the kiwi framework.

    It has a specific feature not found in other implementations. If
    the datatype is set to pixbuf a Gtk.Image will be constructed from the
    pixbuf and be set as a child for the Button
    """

    allowed_data_types = (str, datetime.date, datetime.datetime, datetime.time,
                          GdkPixbuf.Pixbuf) + number
    __gtype_name__ = 'ProxyButton'

    data_type = GObject.Property(getter=ProxyWidgetMixin.get_data_type,
                                 setter=ProxyWidgetMixin.set_data_type,
                                 type=str,
                                 blurb='Data Type')
    model_attribute = GObject.Property(type=str, blurb='Model attribute')
    gsignal('content-changed')
    gsignal('validation-changed', bool)
    gsignal('validate', object, retval=object)

    def __init__(self):
        Gtk.Button.__init__(self)
        ProxyWidgetMixin.__init__(self)
        self.props.data_type = str

    def read(self):
        if self.data_type == 'Pixbuf':
            image = self.get_image()
            if not image:
                return

            storage_type = image.get_storage_type()
            if storage_type != Gtk.ImageType.PIXBUF:
                raise ValueError("the image of a ProxyButton must be loaded "
                                 "from a pixbuf, not %s" % storage_type)
            return image.get_pixbuf()
        else:
            return self._from_string(self.get_label())

    def update(self, data):
        if self.data_type == 'Pixbuf':
            if data == ValueUnset:
                data = None

            if not data:
                image = None
            else:
                image = Gtk.Image()
                image.set_from_pixbuf(data)
                image.show()

            self.set_property('image', image)
        else:
            if data is None:
                text = ""
            else:
                text = self._as_string(data)
            self.set_label(text)

        self.emit('content-changed')
Beispiel #25
0
class FadeOut(gobject.GObject):
    """I am a helper class to draw the fading effect of the background
    Call my methods start() and stop() to control the fading.
    """
    gsignal('done')
    gsignal('color-changed', gdk.Color)

    # How long time it'll take before we start (in ms)
    COMPLAIN_DELAY = 500

    MERGE_COLORS_DELAY = 100

    ERROR_COLOR = "#ffd5d5"

    def __init__(self, widget):
        gobject.GObject.__init__(self)
        self._widget = widget
        self._start_color = None
        self._background_timeout_id = -1
        self._countdown_timeout_id = -1
        self._log = Logger('fade')
        self._done = False

    def _merge_colors(self, src_color, dst_color, steps=10):
        """
        Change the background of widget from src_color to dst_color
        in the number of steps specified
        """

        self._log.debug('_merge_colors: %s -> %s' % (src_color, dst_color))

        if not src_color:
            yield False

        rs, gs, bs = src_color.red, src_color.green, src_color.blue
        rd, gd, bd = dst_color.red, dst_color.green, dst_color.blue
        rinc = (rd - rs) / float(steps)
        ginc = (gd - gs) / float(steps)
        binc = (bd - bs) / float(steps)
        for dummy in xrange(steps):
            rs += rinc
            gs += ginc
            bs += binc
            col = gdk.color_parse("#%02X%02X%02X" %
                                  (int(rs) >> 8, int(gs) >> 8, int(bs) >> 8))
            self.emit('color-changed', col)
            yield True

        self.emit('done')
        self._background_timeout_id = -1
        self._done = True
        yield False

    def _start_merging(self):
        # If we changed during the delay
        if self._background_timeout_id != -1:
            self._log.debug('_start_merging: Already running')
            return

        self._log.debug('_start_merging: Starting')
        func = self._merge_colors(self._start_color,
                                  gdk.color_parse(FadeOut.ERROR_COLOR)).next
        self._background_timeout_id = (gobject.timeout_add(
            FadeOut.MERGE_COLORS_DELAY, func))
        self._countdown_timeout_id = -1

    def start(self, color):
        """Schedules a start of the countdown.
        @param color: initial background color
        @returns: True if we could start, False if was already in progress
        """
        if self._background_timeout_id != -1:
            self._log.debug('start: Background change already running')
            return False
        if self._countdown_timeout_id != -1:
            self._log.debug('start: Countdown already running')
            return False
        if self._done:
            self._log.debug('start: Not running, already set')
            return False

        self._start_color = color
        self._log.debug('start: Scheduling')
        self._countdown_timeout_id = gobject.timeout_add(
            FadeOut.COMPLAIN_DELAY, self._start_merging)

        return True

    def stop(self):
        """Stops the fadeout and restores the background color"""
        self._log.debug('Stopping')
        if self._background_timeout_id != -1:
            gobject.source_remove(self._background_timeout_id)
            self._background_timeout_id = -1
        if self._countdown_timeout_id != -1:
            gobject.source_remove(self._countdown_timeout_id)
            self._countdown_timeout_id = -1

        self._widget.update_background(self._start_color)
        self._done = False
Beispiel #26
0
class FieldGrid(gtk.Layout):
    """FieldGrid is a Grid like widget which you can add fields to

    * **field-added** (object): Emitted when a field is added to the grid
    * **field-removed** (object): Emitted when a field is removed
      from the grid
    * ** selection-changed** (object): Emitted when a field is selected or
      deselected by the user.
    """

    gsignal('selection-changed',
            object,
            flags=gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION)
    gsignal('field-added', object)
    gsignal('field-removed', object)

    def __init__(self, font, width, height):
        gtk.Layout.__init__(self)
        self.set_can_focus(True)
        self.drag_dest_set(gtk.DEST_DEFAULT_ALL, [('OBJECTLIST_ROW', 0, 10),
                                                  ('text/uri-list', 0, 11),
                                                  ('_NETSCAPE_URL', 0, 12)],
                           gdk.ACTION_LINK | gdk.ACTION_COPY | gdk.ACTION_MOVE)

        self.font = pango.FontDescription(font)
        self.width = width
        self.height = height
        self._fields = []
        self._moving_field = None
        self._moving_start_x_pointer = 0
        self._moving_start_y_pointer = 0
        self._moving_start_x_position = 0
        self._moving_start_y_position = 0
        self._action_type = FIELD_NONE
        self._selected_field = None

        self._draw_grid = True
        TEXT = '1234567890ABCDEFTI'
        self._layout = self.create_pango_layout(TEXT)
        self._layout.set_font_description(self.font)
        self._field_width = (self._layout.get_pixel_size()[0] / len(TEXT)) + 2
        self._field_height = self._layout.get_pixel_size()[1] + 2

    #
    # Private API
    #

    def _pick_field(self, window_x, window_y):
        for field in self._fields:
            if field.find_at(window_x, window_y):
                return field
        return None

    def _remove_selected_field(self):
        field = self._selected_field
        if field:
            self._remove_field(field)

    def _remove_field(self, field):
        if field == self._selected_field:
            self.select_field(None)

        self._fields.remove(field)
        self.remove(field.widget)
        self.emit('field-removed', field)

    def _add_field(self,
                   name,
                   description,
                   x,
                   y,
                   width=-1,
                   height=1,
                   model=None):
        label = gtk.Label()
        label.set_alignment(0, 0)
        label.set_padding(2, 4)
        if not description:
            description = name
        label.modify_font(self.font)
        field = FieldInfo(self, name, label, x, y, width, height, model)
        field.update_label(description)
        self._fields.append(field)
        self.emit('field-added', field)

        label.connect('size-allocate', self._on_field__size_allocate, field)
        self.put(label, -1, -1)
        return field

    def _set_field_position(self, field, x, y):
        x = clamp(x, 0, self.width - field.width - 1)
        y = clamp(y, 0, self.height - field.height - 1)
        if field.x == x and field.y == y:
            return

        field.x, field.y = x, y

        if field.widget.props.visible:
            self.queue_resize()
        self.emit('selection-changed', field)

    def _resize_field(self, field, width, height):
        width = clamp(width, 1, self.width - field.x - 1)
        height = clamp(height, 1, self.height - field.y - 1)
        if field.width == width and field.height == height:
            return

        field.width, field.height = width, height

        if field.widget.props.visible:
            self.queue_resize()
        self.emit('selection-changed', field)

    def _get_field_from_widget(self, widget):
        for field in self._fields:
            if field.widget == widget:
                return field
        else:
            raise AssertionError

    def _begin_move_field(self, field, x, y, time):
        if self._moving_field is not None:
            raise AssertionError("can't move two fields at once")

        mask = (gdk.BUTTON_RELEASE_MASK | gdk.BUTTON_RELEASE_MASK
                | gdk.POINTER_MOTION_MASK)
        grab = gdk.pointer_grab(self.window, False, mask, None, None,
                                long(time))
        if grab != gdk.GRAB_SUCCESS:
            raise AssertionError("grab failed")

        self._moving_field = field
        self._moving_start_x_pointer = x
        self._moving_start_y_pointer = y
        self._moving_start_x_position = field.x
        self._moving_start_y_position = field.y
        self._moving_start_width = field.width
        self._moving_start_height = field.height
        w, h = field.widget.get_size_request()
        self._moving_start_w, self._moving_start_h = w, h

    def _update_move_field(self, x, y):
        field = self._moving_field
        if not field:
            return

        if self._action_type == FIELD_MOVE:
            dx, dy = self._get_coords(x - self._moving_start_x_pointer,
                                      y - self._moving_start_y_pointer)
            self._set_field_position(field, self._moving_start_x_position + dx,
                                     self._moving_start_y_position + dy)
        elif self._action_type == FIELD_RESIZE:
            dx, dy = self._get_coords(x - self._moving_start_x_pointer,
                                      y - self._moving_start_y_pointer)
            self._resize_field(field, self._moving_start_width + dx,
                               self._moving_start_height + dy)

    def _end_move_field(self, time):
        if not self._moving_field:
            return

        gdk.pointer_ungrab(long(time))
        self._moving_field = None

    def _get_coords(self, x, y):
        """Returns the grid coordinates given absolute coordinates
        :param x: absolute x
        :param y: absoluyte y
        :returns: (gridx, gridy)
        """
        return (int(float(x) / (self._field_width + 1)),
                int(float(y) / (self._field_height + 1)))

    def _move_field(self, movement_type, delta):
        field = self._selected_field
        if not field:
            return True

        x = field.x
        y = field.y
        if movement_type == FIELD_MOVEMENT_VERTICAL:
            y += delta
        elif movement_type == FIELD_MOVEMENT_HORIZONTAL:
            x += delta
        else:
            raise AssertionError

        self._set_field_position(field, x, y)

    def _on_field__size_allocate(self, label, event, field):
        field.allocate(self._field_width + 1, self._field_height + 1)

    #
    # GtkWidget
    #

    def do_realize(self):
        gtk.Layout.do_realize(self)
        # Use the same gdk.window (from gtk.Layout) to capture these events.
        self.window.set_events(self.get_events() | gdk.BUTTON_PRESS_MASK
                               | gdk.BUTTON_RELEASE_MASK | gdk.KEY_PRESS_MASK
                               | gdk.KEY_RELEASE_MASK | gdk.ENTER_NOTIFY_MASK
                               | gdk.LEAVE_NOTIFY_MASK
                               | gdk.POINTER_MOTION_MASK)

        self.modify_bg(gtk.STATE_NORMAL, gdk.color_parse('white'))
        gc = gdk.GC(self.window, line_style=gdk.LINE_ON_OFF_DASH, line_width=2)
        gc.set_rgb_fg_color(gdk.color_parse('blue'))
        self._selection_gc = gc

        gc = gdk.GC(self.window)
        gc.set_rgb_fg_color(gdk.color_parse('grey80'))
        self._grid_gc = gc

        gc = gdk.GC(self.window)
        gc.set_rgb_fg_color(gdk.color_parse('black'))
        self._border_gc = gc

        gc = gdk.GC(self.window)
        gc.set_rgb_fg_color(gdk.color_parse('grey40'))
        self._field_border_gc = gc

    def do_size_request(self, req):
        border_width = 1
        req.width = self.width * (self._field_width +
                                  border_width) + border_width
        req.height = self.height * (self._field_height +
                                    border_width) + border_width

    def do_expose_event(self, event):
        window = event.window

        if not self.get_realized():
            return

        for c in self._fields:
            self.propagate_expose(c.widget, event)

        fw = self._field_width + 1
        fh = self._field_height + 1

        width = (self.width * fw) - 1
        height = (self.height * fh) - 1
        window.draw_rectangle(self._border_gc, False, 0, 0, width + 1,
                              height + 1)

        if self._draw_grid:
            grid_gc = self._grid_gc

            for x in range(self.width):
                window.draw_line(grid_gc, x * fw, 0, x * fw, height)

            for y in range(self.height):
                window.draw_line(grid_gc, 0, y * fh, width, y * fh)

        fields = self._fields[:]
        if self._selected_field:
            gc = self._selection_gc
            field = self._selected_field
            cx, cy, cw, ch = field.widget.allocation
            window.draw_rectangle(gc, False, cx + 1, cy + 1, cw - 2, ch - 2)

            fields.remove(field)

        gc = self._field_border_gc
        for field in fields:
            cx, cy, cw, ch = field.widget.allocation
            window.draw_rectangle(gc, False, cx + 1, cy + 1, cw - 2, ch - 3)

    def do_button_press_event(self, event):
        x, y = int(event.x), int(event.y)

        field = self._pick_field(x, y)
        self.select_field(field)

        self.grab_focus()

        if not field:
            return

        if not self._moving_field:
            if field.get_cursor(x, y):
                self._action_type = FIELD_RESIZE
            else:
                self._action_type = FIELD_MOVE

            self._begin_move_field(field, x, y, event.time)

        return False

    def do_button_release_event(self, event):
        self._update_move_field(int(event.x), int(event.y))
        self._end_move_field(event.time)

        return False

    def do_motion_notify_event(self, event):
        if self._moving_field is not None:
            self._update_move_field(int(event.x), int(event.y))
        else:
            field = self._pick_field(event.x, event.y)
            cursor = None
            if field:
                cursor = field.get_cursor(event.x, event.y)
            self.window.set_cursor(cursor)

    def do_key_press_event(self, event):
        if self._moving_field:
            return

        if event.keyval == keysyms.Up:
            self._move_field(FIELD_MOVEMENT_VERTICAL, -1)
        elif event.keyval == keysyms.Down:
            self._move_field(FIELD_MOVEMENT_VERTICAL, 1)
        elif event.keyval == keysyms.Left:
            self._move_field(FIELD_MOVEMENT_HORIZONTAL, -1)
        elif event.keyval == keysyms.Right:
            self._move_field(FIELD_MOVEMENT_HORIZONTAL, 1)
        elif event.keyval == keysyms.Delete:
            self._remove_selected_field()

        if gtk.Layout.do_key_press_event(self, event):
            return True

        return True

    def do_drag_drop(self, context, x, y, time):
        return True

    # pylint: disable=E1120
    def do_drag_data_received(self, context, x, y, data, info, time):
        if data.type == 'OBJECTLIST_ROW':
            row = pickle.loads(data.data)
            x, y = self._get_coords(x, y)
            if self.objectlist_dnd_handler(row, x, y):
                context.finish(True, False, time)
        elif data.type == '_NETSCAPE_URL':
            d = data.data.split('\n')[1]
            d = d.replace('&', '&amp;')
            x, y = self._get_coords(x, y)
            field = self.add_field(d, x, y)
            field.show()
            self.select_field(field)
            context.finish(True, False, time)

        context.finish(False, False, time)

    # pylint: enable=E1120

    def do_focus(self, direction):
        self.set_can_focus(False)
        res = gtk.Layout.do_focus(self, direction)
        self.set_can_focus(True)

        return res

    #
    # Public API
    #

    def add_field(self,
                  text,
                  description,
                  x,
                  y,
                  width=-1,
                  height=1,
                  model=None):
        """Adds a new field to the grid

        :param text: text of the field
        :param description: description of the field
        :param x: x position of the field
        :param y: y position of the field
        """
        return self._add_field(text, description, x, y, width, height, model)

    def select_field(self, field):
        """Selects a field
        :param field: the field to select, must be FieldInfo or None
        """
        if field == self._selected_field:
            return
        self._selected_field = field
        self.queue_resize()
        self.grab_focus()
        self.emit('selection-changed', field)

    def get_selected_field(self):
        """ Returns the currently selected field
        :returns: the currently selected field
        :rtype: FieldInfo
        """
        return self._selected_field

    def get_fields(self):
        """ Returns a list of fields in the grid
        :returns: a list of fields in the grid
        """
        return self._fields

    def objectlist_dnd_handler(self, item, x, y):
        """A subclass can implement this to support dnd from
        an ObjectList.
        :param item: the row dragged from the objectlist
        :param x: the x position it was dragged to
        :param y: the y position it was dragged to
        """
        return False

    def resize(self, width, height):
        """
        Resize the grid.
        :param width: the new width
        :param height: the new height
        """
        self.width = width
        self.height = height
        self.queue_resize()
Beispiel #27
0
class BaseActions(GObject.GObject):
    #: Emitted when an object gets created.
    gsignal('model-created', object)

    #: Emitted when one or more objects get changed. Note that the object edited might be unkown
    gsignal('model-edited', object)

    #: Emitted when the model of this action group gets set. Might be useful for plugins that extend
    #: behavior of some domain.
    gsignal('model-set', object)

    #: The name of this action group. Will be used as a prefix for action names
    group_name = None

    @classmethod
    def get_instance(cls):
        if hasattr(cls, '_instance'):
            return cls._instance

        cls._instance = cls()
        return cls._instance

    def __init__(self):
        assert self.group_name
        assert not hasattr(self, '_instance')

        self.model = None
        self._actions = {}
        super(BaseActions, self).__init__()

        self.group = Gio.SimpleActionGroup()
        # register actions that were decorated with @action('ActionName')
        for key in dir(self):
            callback = getattr(self, key)
            if hasattr(callback, '__action_spec__'):
                name, = callback.__action_spec__
                self.add_action(name, callback)

        self._register_action_group()

    def _register_action_group(self):
        """Register ourself in the gtk application infrastructure.
        """
        # Register the group in Gtk.Application to make it available to all
        app = Gtk.Application.get_default()
        if not app:
            app = Gtk.Application()
            Gtk.Application.set_default(app)

        window = app.get_active_window()
        if window:
            window.insert_action_group(self.group_name, self.group)
        else:
            # There is no window yet, so we must wait for one to be added to the applicattion
            def _window_added(app, window):
                window.insert_action_group(self.group_name, self.group)
                app.disconnect(self._conn_id)
            self._conn_id = app.connect('window-added', _window_added)

    def _wrapper_callback(self, action, parameter, original_callback):
        # XXX: We are passing self.model to all callbacks, even if they do not need it.
        original_callback(self.model)

    def add_action(self, name, callback):
        action = Gio.SimpleAction.new(name, None)
        # Don't connect the original callback directly so we can better handle the parameter
        # argument Gio.Action uses.
        action.connect('activate', self._wrapper_callback, callback)
        self.group.add_action(action)
        self._actions[name] = action

    def get_action(self, name):
        """Returns the Gio.Action given its name"""
        return self._actions[name]

    def run_dialog(self, dialog, *args, **kwargs):
        return run_dialog(dialog, None, *args, **kwargs)

    def set_action_enabled(self, action, sensitive):
        """Enables or disables an action

        :param action: the action name
        :param sensitive: If the action is enabled or disabled
        """
        action = self._actions[action]
        action.set_enabled(bool(sensitive))

    def set_model(self, model):
        """Sets the model this action group is currently handling.

        For actions that change the state of an object, this is the object that will be updated.
        """
        self.model = model
        self.model_set(model)
        self.emit('model-set', model)
Beispiel #28
0
class ProxyEntry(KiwiEntry, ValidatableProxyWidgetMixin):
    """The Kiwi Entry widget has many special features that extend the basic
    gtk entry.

    First of all, as every Kiwi Widget, it implements the Proxy protocol.
    As the users types the entry can interact with the application model
    automatically.
    Kiwi Entry also implements interesting UI additions. If the input data
    does not match the data type of the entry the background nicely fades
    to a light red color. As the background changes an information icon
    appears. When the user passes the mouse over the information icon a
    tooltip is displayed informing the user how to correctly fill the
    entry. When dealing with date and float data-type the information on
    how to fill these entries is displayed according to the current locale.
    """

    __class__ = ProxyEntryMeta

    allowed_data_types = (basestring, datetime.date, datetime.time,
                          datetime.datetime, object) + number

    __gtype_name__ = 'ProxyEntry'
    mandatory = gobject.property(type=bool, default=False)
    model_attribute = gobject.property(type=str, blurb='Model attribute')
    gsignal('content-changed')
    gsignal('validation-changed', bool)
    gsignal('validate', object, retval=object)

    def __init__(self, data_type=None):
        self._block_changed = False
        self._has_been_updated = False
        KiwiEntry.__init__(self)
        ValidatableProxyWidgetMixin.__init__(self)
        self._entry_data_type = data_type
        # XXX: Sales -> New Loan Item requires this, figure out why
        try:
            self.props.data_type = data_type
        except (AttributeError, TypeError):
            pass
        # Hide currency symbol from the entry.
        self.set_options_for_datatype(currency, symbol=False)

    def __post_init__(self):
        self.props.data_type = self._entry_data_type

    # Virtual methods
    gsignal('changed', 'override')

    def do_changed(self):
        if self._block_changed:
            self.emit_stop_by_name('changed')
            return
        self._update_current_object(self.get_text())
        self.emit('content-changed')

    def _set_data_type(self, data_type):
        if not ProxyWidgetMixin.set_data_type(self, data_type):
            return

        # Numbers and dates should be right aligned
        conv = converter.get_converter(data_type)
        if conv.align == Alignment.RIGHT:
            self.set_property('xalign', 1.0)

        # Apply a mask for the data types, some types like
        # dates has a default mask
        try:
            self.set_mask_for_data_type(data_type)
        except MaskError:
            pass

    data_type = gobject.property(getter=ProxyWidgetMixin.get_data_type,
                                 setter=_set_data_type,
                                 type=str,
                                 blurb='Data Type')

    # Public API

    def set_mask_for_data_type(self, data_type):
        """
        Set a mask for the parameter data_type.
        @param data_type:
        """
        conv = converter.get_converter(data_type)
        mask = conv.get_mask()
        self.set_mask(mask)

    #@deprecated('prefill')
    def set_completion_strings(self, strings=[], values=[]):
        """
        Set strings used for entry completion.
        If values are provided, each string will have an additional
        data type.

        @param strings:
        @type  strings: list of strings
        @param values:
        @type  values: list of values
        """

        completion = self._get_entry_completion()
        model = completion.get_model()
        model.clear()

        if values:
            self._mode = ENTRY_MODE_DATA
            self.prefill(zip(strings, values))
        else:
            self._mode = ENTRY_MODE_TEXT
            self.prefill(strings)

    set_completion_strings = deprecated('prefill')(set_completion_strings)

    def set_text(self, text):
        """
        Sets the text of the entry

        @param text:
        """

        self._has_been_updated = True
        self._update_current_object(text)

        # If content isn't empty set_text emitts changed twice.
        # Protect content-changed from being updated and issue
        # a manual emission afterwards
        self._block_changed = True
        KiwiEntry.set_text(self, text)
        self._block_changed = False
        self.emit('content-changed')

        self.set_position(-1)

    # ProxyWidgetMixin implementation

    def read(self):
        mode = self._mode
        if mode == ENTRY_MODE_TEXT:
            # Do not consider masks which only displays static
            # characters invalid, instead return None
            if not self._has_been_updated:
                return ValueUnset
            text = self.get_text()
            return self._from_string(text)
        elif mode == ENTRY_MODE_DATA:
            return self._current_object
        else:
            raise AssertionError

    def update(self, data):
        if data is ValueUnset:
            self.set_text("")
            self._has_been_updated = False
        elif data is None:
            self.set_text("")
        else:
            mode = self._mode
            if mode == ENTRY_MODE_DATA:
                new = self._get_text_from_object(data)
                if new is None:
                    raise TypeError("%r is not a data object" % data)
                text = new
            elif mode == ENTRY_MODE_TEXT:
                text = self._as_string(data)
            self.set_text(text)
Beispiel #29
0
class ProxySpinButton(Gtk.SpinButton, ValidatableProxyWidgetMixin):
    """
    A SpinButton subclass which adds supports for the Kiwi Framework.
    This widget supports validation
    The only allowed types for spinbutton are int and float.

    """
    __gtype_name__ = 'ProxySpinButton'

    data_type = GObject.Property(getter=ProxyWidgetMixin.get_data_type,
                                 setter=ProxyWidgetMixin.set_data_type,
                                 type=str,
                                 blurb='Data Type')
    mandatory = GObject.Property(type=bool, default=False)
    model_attribute = GObject.Property(type=str, blurb='Model attribute')
    gsignal('content-changed')
    gsignal('validation-changed', bool)
    gsignal('validate', object, retval=object)

    allowed_data_types = number

    def __init__(self, data_type=int):
        # since the default data_type is str we need to set it to int
        # or float for spinbuttons
        Gtk.SpinButton.__init__(self)
        ValidatableProxyWidgetMixin.__init__(self)
        self.props.data_type = data_type
        self.set_property('xalign', 1.0)
        self.set_property("truncate-multiline", True)

        # We need to do this because spinbuttons are supposed to accept only
        # numbers.
        self.set_numeric(True)

        # This used to be an override, but after the gtk3 migration if we
        # use the override or create a do_changed method GObject will break
        # the object in a way that it will be considered a Gtk.SpinButton
        # directly instead of a ProxySpinButton. The side effect of that
        # would be that out custom events (e.g. validate) would not exist.
        self.connect('changed', self._on_changed)

    def _on_changed(self, widget):
        """Called when the content of the spinbutton changes.
        """
        # This is a work around, because GtkEditable.changed is called too
        # often, as reported here: http://bugzilla.gnome.org/show_bug.cgi?id=64998
        if self.get_text() != '':
            self.emit('content-changed')

    def read(self):
        return self._from_string(self.get_text())

    def update(self, data):
        if data is None or data is ValueUnset:
            if self.props.mandatory and self.get_text() != "":
                self.emit('validation-changed', False)
            self.set_text("")
        else:
            if self.props.mandatory and self.get_text() == "":
                self.emit('validation-changed', True)
            # set_value accepts a float or int, no as_string conversion needed,
            # and since we accept only int and float just send it in.
            self.set_value(data)

    # Old IconEntry API

    def set_tooltip(self, text):
        self.set_property('primary-icon-tooltip-text', text)

    def set_pixbuf(self, pixbuf):
        # Spinbuttons are always right aligned
        self.set_property('primary-icon-pixbuf', pixbuf)

    def add_css_class(self, css_class):
        sc = self.get_style_context()
        sc.add_class(css_class)

    def remove_css_class(self, css_class):
        sc = self.get_style_context()
        sc.remove_class(css_class)
Beispiel #30
0
class KiwiEntryCompletion(gtk.EntryCompletion):
    def __init__(self):
        gtk.EntryCompletion.__init__(self)

        self._inline_completion = False
        self._popup_completion = True
        self._entry = None
        self._completion_timeout = -1
        self._match_function = None
        self._match_function_data = None
        self._key = None
        self.changed_id = 0

        self._filter_model = None
        self._treeview = None

        self._popup_window = None
        self._selected_index = -1

    gsignal('match-selected', 'override')

    def do_match_selected(self, model, iter):
        self._entry.set_text(model[iter][0])
        return True

    def _visible_function(self, model, iter, data=None):
        if not self._entry:
            return False

        if not self._key:
            return False

        if self._match_function:
            return self._match_function(self, self._key, iter)

        value = model[iter][0]
        if not value:
            return False

        entry_text = self._entry.get_text()
        if self._entry.completion_ignore_case:
            entry_text = entry_text.lower()
            value = value.lower()
        if self._entry.completion_ignore_accents:
            entry_text = strip_accents(entry_text)
            value = strip_accents(value)

        return value.startswith(entry_text)

    def _connect_completion_signals(self):
        if self._popup_completion:
            self.changed_id = self._entry.connect('changed',
                                                  self._on_completion_changed)

            self._entry.connect('key-press-event',
                                self._on_completion_key_press)

            self._entry.connect('button-press-event',
                                self._on_button_press_event)

    def _on_button_press_event(self, window, event):
        # If we're clicking outside of the window, close the popup
        if not self._popup_window:
            return

        if (event.window != self._popup_window.get_window() or (tuple(
                self._popup_window.allocation.intersect(
                    gdk.Rectangle(
                        x=int(event.x), y=int(event.y), width=1, height=1))))
                == (0, 0, 0, 0)):
            self.popdown()

    def _on_completion_timeout(self):
        minimum_key_length = self.get_property('minimum-key-length')
        if (self._filter_model
                and len(self._entry.get_text()) >= minimum_key_length
                and self._entry.is_focus()):
            self.complete()
            matches = self._filter_model.iter_n_children(None)
            if matches:
                self.popup()

        return False

    def _on_completion_changed(self, entry):
        if (self.get_property('minimum_key_length') > 0
                and not self._entry.get_text()):
            self.popdown()
            return

        self._selected_index = -1

        if self._completion_timeout != -1:
            gobject.source_remove(self._completion_timeout)

        timeout = gobject.timeout_add(COMPLETION_TIMEOUT,
                                      self._on_completion_timeout)
        self._completion_timeout = timeout
        return True

    def _select_item(self, index):
        # Make the selection
        matches = self._filter_model.iter_n_children(None)

        if 0 <= index < matches:
            self._treeview.set_cursor((index, ))
        else:
            selection = self._treeview.get_selection()
            selection.unselect_all()

        self._selected_index = index

    def _on_completion_key_press(self, entry, event):
        window = self._popup_window
        if window and not window.flags() & gtk.VISIBLE:
            return False

        if not self._treeview:
            return False

        matches = self._filter_model.iter_n_children(None)
        keyval = event.keyval
        index = self._selected_index

        if keyval == keysyms.Up or keyval == keysyms.KP_Up:
            index -= 1
            if index < -1:
                index = matches - 1

            self._select_item(index)
            return True

        elif keyval == keysyms.Down or keyval == keysyms.KP_Down:
            index += 1
            if index > matches - 1:
                index = -1

            self._select_item(index)
            return True

        elif keyval == keysyms.Page_Up:
            if index < 0:
                index = matches - 1
            elif index > 0 and index - PAGE_INCREMENT < 0:
                index = 0
            else:
                index -= PAGE_INCREMENT

            if index < 0:
                index = -1

            self._select_item(index)
            return True

        elif keyval == keysyms.Page_Down:
            if index < 0:
                index = 0
            elif index < matches - 1 and index + PAGE_INCREMENT > matches - 1:
                index = matches - 1
            else:
                index += PAGE_INCREMENT

            if index > matches:
                index = -1

            self._select_item(index)
            return True

        elif keyval == keysyms.Escape:
            self.popdown()
            return True

        elif (keyval == keysyms.Return or keyval == keysyms.KP_Enter):
            self.popdown()
            selection = self._treeview.get_selection()
            model, titer = selection.get_selected()
            if not titer:
                return False

            self._entry.handler_block(self.changed_id)
            self.emit('match-selected', model, titer)
            self._entry.handler_unblock(self.changed_id)
            selection.unselect_all()
            return True

        return False

    def _popup_grab_window(self):
        activate_time = 0L
        window = self._entry.get_window()
        if gdk.pointer_grab(window, True,
                            (gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK
                             | gdk.POINTER_MOTION_MASK), None, None,
                            activate_time) == 0:
            if gdk.keyboard_grab(window, True, activate_time) == 0:
                return True
            else:
                window.get_display().pointer_ungrab(activate_time)
                return False
        return False

    def _popup_ungrab_window(self):
        activate_time = 0L
        display = self._entry.get_window().get_display()
        display.pointer_ungrab(activate_time)
        display.keyboard_ungrab(activate_time)

    # Public API
    def complete(self):
        if not self._filter_model:
            return

        self._key = self._entry.get_text()
        self._filter_model.refilter()
        self._treeview.set_model(self._filter_model)
        if self._treeview.flags() & gtk.REALIZED:
            self._treeview.scroll_to_point(0, 0)

    def set_entry(self, entry):
        self._entry = entry
        self._connect_completion_signals()

    def get_entry(self):
        return self._entry

    def set_popup_window(self, window):
        self._popup_window = window

    def set_treeview(self, treeview):
        self._treeview = treeview

    def get_treeview(self):
        return self._treeview

    def popup(self):
        if not self._popup_window:
            return

        self._popup_window.popup(text=None, filter=True)
        self._popup_grab_window()

    def popdown(self):
        if not self._popup_window:
            return

        self._popup_window.popdown()
        self._popup_ungrab_window()

    def set_model(self, model):
        if not model:
            if self._popup_window:
                self._popup_window.set_model(None)
            self.popdown()
            self._model = None
            self._filter_model = None
            return

        self._model = model
        self._filter_model = model.filter_new()
        self._filter_model.set_visible_func(self._visible_function)
        if self._popup_window:
            self._popup_window.set_model(self._filter_model)

    def get_model(self):
        return self._model

    def set_match_func(self, function, data=None):
        self._match_function = function
        self._match_function_data = data