Source code for kiwi.ui.entry

#
# Kiwi: a Framework and Enhanced Widgets for Python
#
# Copyright (C) 2006 Async Open Source
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
# USA
#
# Author(s): Johan Dahlin <jdahlin@async.com.br>
#            Ronaldo Maia <romaia@async.com.br>
#
#
# Design notes:
#
#   When inserting new text, supose, the entry, at some time is like this,
#   ahd the user presses '0', for instance:
#   --------------------------------
#   | ( 1 2 )   3 4 5   - 6 7 8 9  |
#   --------------------------------
#              ^ ^     ^
#              S P     E
#
#   S - start of the field (start)
#   E - end of the field (end)
#   P - pos - where the new text is being inserted. (pos)
#
#   So, the new text will be:
#
#     the old text, from 0 until P
#   + the new text
#   + the old text, from P until the end of the field, shifted to the
#     right
#   + the old text, from the end of the field, to the end of the text.
#
#   After inserting, the text will be this:
#   --------------------------------
#   | ( 1 2 )   3 0 4 5 - 6 7 8 9  |
#   --------------------------------
#              ^   ^   ^
#              S   P   E
#
#
#   When deleting some text, supose, the entry, at some time is like this:
#   --------------------------------
#   | ( 1 2 )   3 4 5 6 - 7 8 9 0  |
#   --------------------------------
#              ^ ^ ^   ^
#              S s e   E
#
#   S - start of the field (_start)
#   E - end of the field (_end)
#   s - start of the text being deleted (start)
#   e - end of the text being deleted (end)
#
#   end - start -> the number of characters being deleted.
#
#   So, the new text will be:
#
#     the old text, from 0 until the start of the text being deleted.
#   + the old text, from the start of where the text is being deleted, until
#     the end of the field, shifted to the left, end-start positions
#   + the old text, from the end of the field, to the end of the text.
#
#   So, after the text is deleted, the entry will look like this:
#
#   --------------------------------
#   | ( 1 2 )   3 5 6   - 7 8 9 0  |
#   --------------------------------
#                ^
#                P
#
#   P = the position of the cursor after the deletion, witch is equal to
#   start (s at the previous illustration)


"""
An enchanced version of GtkEntry that supports icons and masks
"""

import gettext
import string
try:
    set
except NameError:
    from sets import Set as set

import gobject
import pango
import gtk

from kiwi.enums import Direction
from kiwi.ui.entrycompletion import KiwiEntryCompletion
from kiwi.utils import type_register

# In gtk+ 2.18 they refactored gtk.Entry to use gtk.EntryBuffer, so we need to
# track the gtk version here for later use.
if gtk.gtk_version >= (2, 18):
    GTK_2_18 = True
else:
    GTK_2_18 = False


[docs]class MaskError(Exception): pass
(INPUT_ASCII_LETTER, INPUT_ALPHA, INPUT_ALPHANUMERIC, INPUT_DIGIT) = range(4) INPUT_FORMATS = { '0': INPUT_DIGIT, 'L': INPUT_ASCII_LETTER, 'A': INPUT_ALPHANUMERIC, 'a': INPUT_ALPHANUMERIC, '&': INPUT_ALPHA, } # Todo list: Other usefull Masks # 9 - Digit, optional # ? - Ascii letter, optional # C - Alpha, optional INPUT_CHAR_MAP = { INPUT_ASCII_LETTER: lambda text: text in string.ascii_letters, INPUT_ALPHA: unicode.isalpha, INPUT_ALPHANUMERIC: unicode.isalnum, INPUT_DIGIT: unicode.isdigit, } (COL_TEXT, COL_OBJECT) = range(2) (ENTRY_MODE_UNKNOWN, ENTRY_MODE_TEXT, ENTRY_MODE_DATA) = range(3) _ = lambda msg: gettext.dgettext('kiwi', msg)
[docs]class KiwiEntry(gtk.Entry): """ The KiwiEntry is a Entry subclass with the following additions: - Mask, force the input to meet certain requirements - IComboMixin: Allows you work with objects instead of strings Adds a number of convenience methods such as :class:`prefill`(). """ __gtype_name__ = 'KiwiEntry' def __init__(self): self._completion = None gtk.Entry.__init__(self) self._update_position() self.connect('insert-text', self._on_insert_text) self.connect('delete-text', self._on_delete_text) self.connect_after('grab-focus', self._after_grab_focus) self.connect('changed', self._on_changed) self.connect('focus', self._on_focus) self.connect('focus-out-event', self._on_focus_out_event) self.connect('move-cursor', self._on_move_cursor) # Ideally, this should be connected to notify::cursor-position, but # there seems to be a bug in gtk that the notification is not emited # when it should. # TODO: investigate that and report a bug. self.connect('notify::selection-bound', self._on_notify_selection_bound) self.connect('notify::xalign', self._on_notify_xalign) self._block_changed = False self._current_object = None self._mode = ENTRY_MODE_TEXT # List of validators # str -> static characters # int -> dynamic, according to constants above self._mask_validators = [] self._mask = None # Fields defined by mask # each item is a tuble, containing the begining and the end of the # field in the text self._mask_fields = [] self._current_field = -1 self._pos = 0 self._selecting = False self._prop_completion = False self._exact_completion = False self._block_insert = False self._block_delete = False # Properties def _get_exact_completion(self): return self._exact_completion def _set_exact_completion(self, value): self.set_exact_completion(value) self._exact_completion = value exact_completion = gobject.property(getter=_get_exact_completion, setter=_set_exact_completion, type=bool, default=False) def _prop_get_completion(self): return self._prop_completion def _prop_set_completion(self, value): if not self.get_completion(): self.set_completion(gtk.EntryCompletion()) self._prop_completion = value completion = gobject.property(getter=_prop_get_completion, setter=_prop_set_completion, type=bool, default=False) def _get_mask(self): return self._mask def _set_mask(self, value): try: self.set_mask(value) return self.get_mask() except MaskError, e: pass mask = gobject.property(getter=_get_mask, setter=_set_mask, type=str, default='') # Public API
[docs] def set_text(self, text): completion = self.get_completion() if isinstance(completion, KiwiEntryCompletion): self.handler_block(completion.changed_id) gtk.Entry.set_text(self, text) if GTK_2_18: self._do_2_18_workaround(text) if isinstance(completion, KiwiEntryCompletion): self.handler_unblock(completion.changed_id)
def _do_2_18_workaround(self, text): # FIXME: When using gtk 2.18 and pygtk 2.16 our set_text method is # broken because we edit the text that will stay in the entry (mask # feature). My guess is that the signal 'insert-text' have not been # emitted when calling gtk_entry_set_text method (but the signal is # emitted in gtk_entry_insert_text method, see gtkentry.c in gtk+). self._really_delete_text(0, -1) if not self._mask: new_text = text self._really_insert_text(text, 0) return if not text: # set_text used to clean the entry, but the mask must stay there. self._really_insert_text(self.get_empty_mask(), 0) return to_insert = [] for i in self._mask_validators: if isinstance(i, int): # mark available positions with an empty string to_insert.append('') else: to_insert.append(i) for t in unicode(text): # find the next position available for insertion for pos, k in enumerate(to_insert): if k == '': break if self._confirms_to_mask(pos, t): to_insert[pos] = t self._really_insert_text(''.join(to_insert), 0) self.set_position(pos+1) # Mask & Fields
[docs] def set_mask(self, mask): """ Sets the mask of the Entry. Supported format characters are: - '0' digit - 'L' ascii letter (a-z and A-Z) - '&' alphabet, honors the locale - 'a' alphanumeric, honors the locale - 'A' alphanumeric, honors the locale This is similar to MaskedTextBox: U{http://msdn2.microsoft.com/en-us/library/system.windows.forms.maskedtextbox.mask(VS.80).aspx} Example mask for a ISO-8601 date >>> entry.set_mask('0000-00-00') :param mask: the mask to set """ if not mask: self.modify_font(pango.FontDescription("sans")) self._mask = mask return # First, reset self._mask_validators = [] self._mask_fields = [] self._current_field = -1 mask = unicode(mask) input_length = len(mask) lenght = 0 pos = 0 field_begin = 0 field_end = 0 while True: if pos >= input_length: break if mask[pos] in INPUT_FORMATS: self._mask_validators += [INPUT_FORMATS[mask[pos]]] field_end += 1 else: self._mask_validators.append(mask[pos]) if field_begin != field_end: self._mask_fields.append((field_begin, field_end)) field_end += 1 field_begin = field_end pos += 1 self._mask_fields.append((field_begin, field_end)) self.modify_font(pango.FontDescription("monospace")) self._really_delete_text(0, -1) self._insert_mask(0, input_length) self._mask = mask
[docs] def get_mask(self): """ Get the mask. :returns: the mask """ return self._mask
[docs] def get_field_text(self, field): if not self._mask: raise MaskError("a mask must be set before calling get_field_text") text = self.get_text() start, end = self._mask_fields[field] return text[start: end].strip()
[docs] def get_fields(self): """ Get the fields assosiated with the entry. A field is dynamic content separated by static. For example, the format string 000-000 has two fields separated by a dash. if a field is empty it'll return an empty string otherwise it'll include the content :returns: fields :rtype: list of strings """ if not self._mask: raise MaskError("a mask must be set before calling get_fields") fields = [] text = unicode(self.get_text()) for start, end in self._mask_fields: fields.append(text[start:end].strip()) return fields
[docs] def get_empty_mask(self, start=None, end=None): """ Gets the empty mask between start and end :param start: :param end: :returns: mask :rtype: string """ if start is None: start = 0 if end is None: end = len(self._mask_validators) s = '' for validator in self._mask_validators[start:end]: if isinstance(validator, int): s += ' ' elif isinstance(validator, unicode): s += validator else: raise AssertionError return s
[docs] def get_field_pos(self, field): """ Get the position at the specified field. """ if field >= len(self._mask_fields): return None start, end = self._mask_fields[field] return start
def _get_field_ideal_pos(self, field): start, end = self._mask_fields[field] text = self.get_field_text(field) pos = start+len(text) return pos
[docs] def get_field(self): if self._current_field >= 0: return self._current_field else: return None
[docs] def set_field(self, field, select=False): if field >= len(self._mask_fields): return pos = self._get_field_ideal_pos(field) self.set_position(pos) if select: field_text = self.get_field_text(field) start, end = self._mask_fields[field] self.select_region(start, pos) self._current_field = field
[docs] def get_field_length(self, field): if 0 <= field < len(self._mask_fields): start, end = self._mask_fields[field] return end - start
def _shift_text(self, start, end, direction=Direction.LEFT, positions=1): """ Shift the text, to the right or left, n positions. Note that this does not change the entry text. It returns the shifted text. :param start: :param end: :param direction: see :class:`kiwi.enums.Direction` :param positions: the number of positions to shift. :return: returns the text between start and end, shifted to the direction provided. """ text = self.get_text() new_text = '' validators = self._mask_validators if direction == Direction.LEFT: i = start else: i = end - 1 # When shifting a text, we wanna keep the static chars where they # are, and move the non-static chars to the right position. while start <= i < end: if isinstance(validators[i], int): # Non-static char shoud be here. Get the next one (depending # on the direction, and the number of positions to skip.) # # When shifting left, the next char will be on the right, # so, it will be appended, to the new text. # Otherwise, when shifting right, the char will be # prepended. next_pos = self._get_next_non_static_char_pos(i, direction, positions-1) # If its outside the bounds of the region, ignore it. if not start <= next_pos <= end: next_pos = None if next_pos is not None: if direction == Direction.LEFT: new_text = new_text + text[next_pos] else: new_text = text[next_pos] + new_text else: if direction == Direction.LEFT: new_text = new_text + ' ' else: new_text = ' ' + new_text else: # Keep the static char where it is. if direction == Direction.LEFT: new_text = new_text + text[i] else: new_text = text[i] + new_text i += direction return new_text def _get_next_non_static_char_pos(self, pos, direction=Direction.LEFT, skip=0): """ Get next non-static char position, skiping some chars, if necessary. :param skip: skip first n chars :param direction: direction of the search. """ text = self.get_text() validators = self._mask_validators i = pos+direction+skip while 0 <= i < len(text): if isinstance(validators[i], int): return i i += direction return None def _get_field_at_pos(self, pos, dir=None): """ Return the field index at position pos. """ for p in self._mask_fields: if p[0] <= pos <= p[1]: return self._mask_fields.index(p) return None
[docs] def set_exact_completion(self, value): """ Enable exact entry completion. Exact means it needs to start with the value typed and the case needs to be correct. :param value: enable exact completion :type value: boolean """ if value: match_func = self._completion_exact_match_func else: match_func = self._completion_normal_match_func completion = self._get_entry_completion() completion.set_match_func(match_func)
[docs] def is_empty(self): text = self.get_text() if self._mask: empty = self.get_empty_mask() else: empty = '' return text == empty # Private
def _really_delete_text(self, start, end): # A variant of delete_text() that never is blocked by us self._block_delete = True self.delete_text(start, end) self._block_delete = False def _really_insert_text(self, text, position): # A variant of insert_text() that never is blocked by us self._block_insert = True self.insert_text(text, position) self._block_insert = False def _insert_mask(self, start, end): text = self.get_empty_mask(start, end) self._really_insert_text(text, position=start) def _confirms_to_mask(self, position, text): validators = self._mask_validators if position < 0 or position >= len(validators): return False validator = validators[position] if isinstance(validator, int): if not INPUT_CHAR_MAP[validator](text): return False if isinstance(validator, unicode): if validator == text: return True return False return True def _update_current_object(self, text): if self._mode != ENTRY_MODE_DATA: return self._current_object = None for row in self.get_completion().get_model(): if row[COL_TEXT] == text: self._current_object = row[COL_OBJECT] break treeview = self.get_completion().get_treeview() model = treeview.get_model() selection = treeview.get_selection() if self._current_object: treeiter = row.iter if isinstance(model, gtk.TreeModelFilter) and treeiter: # Just like we do in comboentry.py, convert iter between # models. See #3099 for mor information tmodel = model.get_model() if tmodel.iter_is_valid(treeiter): treeview.set_model(tmodel) selection = treeview.get_selection() else: treeiter = model.convert_child_iter_to_iter(treeiter) selection.select_iter(treeiter) self.set_valid() elif text: selection.unselect_all() self.set_invalid(_("'%s' is not a valid object" % text)) elif self.mandatory: selection.unselect_all() self.set_blank() def _get_text_from_object(self, obj): if self._mode != ENTRY_MODE_DATA: return for row in self.get_completion().get_model(): if row[COL_OBJECT] == obj: return row[COL_TEXT] def _get_entry_completion(self): # Check so we have completion enabled, not this does not # depend on the property, the user can manually override it, # as long as there is a completion object set completion = self.get_completion() if completion: return completion completion = gtk.EntryCompletion() self.set_completion(completion) return completion
[docs] def get_completion(self): return self._completion
[docs] def set_completion(self, completion): if not isinstance(completion, KiwiEntryCompletion): gtk.Entry.set_completion(self, completion) completion.set_model(gtk.ListStore(str, object)) completion.set_text_column(0) self._completion = gtk.Entry.get_completion(self) return old = self.get_completion() if old == completion: return completion if old and isinstance(old, KiwiEntryCompletion): old.disconnect_completion_signals() self._completion = completion # First, tell the completion what entry it will complete completion.set_entry(self) completion.set_model(gtk.ListStore(str, object)) completion.set_text_column(0) self.set_exact_completion(False) completion.connect("match-selected", self._on_completion__match_selected) self._current_object = None return completion
def _completion_exact_match_func(self, completion, key, iter): model = completion.get_model() if not len(model): return content = model[iter][COL_TEXT] return key.startswith(content) def _completion_normal_match_func(self, completion, key, iter): model = completion.get_model() if not len(model): return raw_content = model[iter][COL_TEXT] if raw_content is not None: return key.lower() in raw_content.lower() else: return False def _on_completion__match_selected(self, completion, model, iter): if not len(model): return # this updates current_object and triggers content-changed self.set_text(model[iter][COL_TEXT]) self.set_position(-1) # FIXME: Enable this at some point #self.activate() def _appers_later(self, char, start): """ Check if a char appers later on the mask. If it does, return the field it appers at. returns False otherwise. """ validators = self._mask_validators i = start while i < len(validators): if self._mask_validators[i] == char: field = self._get_field_at_pos(i) if field is None: return False return field i += 1 return False def _can_insert_at_pos(self, new, pos): """ Check if a chararcter can be inserted at some position :param new: The char that wants to be inserted. :param pos: The position where it wants to be inserted. :return: Returns None if it can be inserted. If it cannot be, return the next position where it can be successfuly inserted. """ validators = self._mask_validators # Do not let insert if the field is full field = self._get_field_at_pos(pos) if field is not None: text = self.get_field_text(field) length = self.get_field_length(field) if len(text) == length: gtk.gdk.beep() return pos # If the char confirms to the mask, but is a static char, return the # position after that static char. if (self._confirms_to_mask(pos, new) and not isinstance(validators[pos], int)): return pos+1 # If does not confirms to mask: # - Check if the char the user just tried to enter appers later. # - If it does, Jump to the start of the field after that if not self._confirms_to_mask(pos, new): field = self._appers_later(new, pos) if field is not False: pos = self.get_field_pos(field+1) if pos is not None: gobject.idle_add(self.set_position, pos) return pos return None def _insert_at_pos(self, text, new, pos): """ Inserts the character at the give position in text. Note that the insertion won't be applied to the entry, but to the text provided. :param text: Text that it will be inserted into. :param new: New text to insert. :param pos: Positon to insert at :return: Returns a tuple, with the position after the insetion and the new text. """ field = self._get_field_at_pos(pos) length = len(new) new_pos = pos start, end = self._mask_fields[field] # Shift Right new_text = (text[:pos] + new + self._shift_text(pos, end, Direction.RIGHT)[1:] + text[end:]) # Overwrite Right # new_text = (text[:pos] + new + # text[pos+length:end]+ # text[end:]) new_pos = pos+1 gobject.idle_add(self.set_position, new_pos) # If the field is full, jump to the next field if len(self.get_field_text(field)) == self.get_field_length(field)-1: gobject.idle_add(self.set_field, field+1, True) self.set_field(field+1) return new_pos, new_text def _update_position(self): if self.get_property('xalign') > 0.5: self._icon_pos = gtk.POS_LEFT else: self._icon_pos = gtk.POS_RIGHT # If the text is right to left, we have to use the oposite side RTL = gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL if RTL: if self._icon_pos == gtk.POS_LEFT: self._icon_pos = gtk.POS_RIGHT else: self._icon_pos = gtk.POS_LEFT # Callbacks def _on_insert_text(self, editable, new, length, position): if not self._mask or self._block_insert: return new = unicode(new) pos = self.get_position() self.stop_emission('insert-text') text = self.get_text() # Insert one char at a time for c in new: _pos = self._can_insert_at_pos(c, pos) if _pos is None: pos, text = self._insert_at_pos(text, c, pos) else: pos = _pos # Change the text with the new text. self._block_changed = True self._really_delete_text(0, -1) self._block_changed = False self._really_insert_text(text, 0) def _on_delete_text(self, editable, start, end): if not self._mask or self._block_delete: return self.stop_emission('delete-text') pos = self.get_position() # Trying to delete an static char. Delete the char before that if (0 < start < len(self._mask_validators) and not isinstance(self._mask_validators[start], int) and pos != start): self._on_delete_text(editable, start-1, start) return # we just tried to delete, stop the selection. self._selecting = False field = self._get_field_at_pos(end-1) # Outside a field. Cannot delete. if field is None: self.set_position(end-1) return _start, _end = self._mask_fields[field] # Deleting from outside the bounds of the field. if start < _start or end > _end: _start, _end = start, end # Change the text text = self.get_text() # Shift Left new_text = (text[:start] + self._shift_text(start, _end, Direction.LEFT, end-start) + text[_end:]) # Overwrite Left # empty_mask = self.get_empty_mask() # new_text = (text[:_start] + # text[_start:start] + # empty_mask[start:start+(end-start)] + # text[start+(end-start):_end] + # text[_end:]) new_pos = start self._block_changed = True self._really_delete_text(0, -1) self._block_changed = False self._really_insert_text(new_text, 0) # Position the cursor on the right place. self.set_position(new_pos) if pos == new_pos: self._handle_position_change() def _after_grab_focus(self, widget): # The text is selectet in grab-focus, so this needs to be done after # that: if self.is_empty(): if self._mask: self.set_field(0) else: self.set_position(0) def _on_focus(self, widget, direction): if not self._mask: return field = self._current_field if (direction == gtk.DIR_TAB_FORWARD or direction == gtk.DIR_DOWN): field += 1 elif (direction == gtk.DIR_TAB_BACKWARD or direction == gtk.DIR_UP): field = -1 # Leaving the entry if field == len(self._mask_fields) or field == -1: self.select_region(0, 0) self._current_field = -1 return False if field < 0: field = len(self._mask_fields)-1 # grab_focus changes the selection, so we need to grab_focus before # making the selection. self.grab_focus() self.set_field(field, select=True) return True def _on_notify_selection_bound(self, widget, pspec): if not self._mask: return if not self.is_focus(): return if self._selecting: return self._handle_position_change() def _on_notify_xalign(self, entry, pspec): self._update_position() def _handle_position_change(self): pos = self.get_position() field = self._get_field_at_pos(pos) # Humm, the pos is not inside any field. Get the next pos inside # some field, depending on the direction that the cursor is # moving diff = pos - self._pos # but move only one position at a time. if diff: diff /= abs(diff) _field = field if diff: while _field is None and pos >= 0: pos += diff _field = self._get_field_at_pos(pos) self._pos = pos if pos < 0: self._pos = self.get_field_pos(0) if field is None: self.set_position(self._pos) else: if self._current_field != -1: self._current_field = field self._pos = pos def _on_changed(self, widget): if self._block_changed: self.stop_emission('changed') def _on_focus_out_event(self, widget, event): if not self._mask: return self._current_field = -1 def _on_move_cursor(self, entry, step, count, extend_selection): self._selecting = extend_selection # Old IconEntry API
[docs] def set_tooltip(self, text): if self._icon_pos == gtk.POS_LEFT: icon = 'primary-icon-tooltip-text' else: icon = 'secondary-icon-tooltip-text' self.set_property(icon, text)
[docs] def set_pixbuf(self, pixbuf): if self._icon_pos == gtk.POS_LEFT: icon = 'primary-icon-pixbuf' else: icon = 'secondary-icon-pixbuf' self.set_property(icon, pixbuf)
[docs] def update_background(self, color): self.modify_base(gtk.STATE_NORMAL, color)
[docs] def get_background(self): return self.style.base[gtk.STATE_NORMAL] # IComboMixin
[docs] def prefill(self, itemdata, sort=False): """ See :class:`kiwi.interfaces.IEasyCombo.prefill` """ if not isinstance(itemdata, (list, tuple)): raise TypeError("'data' parameter must be a list or tuple of item " "descriptions, found %s") % type(itemdata) completion = self._get_entry_completion() model = completion.get_model() if len(itemdata) == 0: model.clear() return if (len(itemdata) > 0 and type(itemdata[0]) in (tuple, list) and len(itemdata[0]) == 2): mode = self._mode = ENTRY_MODE_DATA else: mode = self._mode values = set() if mode == ENTRY_MODE_TEXT: if sort: itemdata.sort() for item in itemdata: if item in values: raise KeyError("Tried to insert duplicate value " "%r into the entry" % item) else: values.add(item) model.append((item, None)) elif mode == ENTRY_MODE_DATA: if sort: itemdata.sort(lambda x, y: cmp(x[0], y[0])) for item in itemdata: text, data = item # Add (n) to the end in case of duplicates count = 1 orig = text while text in values: text = orig + ' (%d)' % count count += 1 values.add(text) model.append((text, data)) else: raise TypeError("Incorrect format for itemdata; see " "docstring for more information")
[docs] def get_iter_by_data(self, data): if self._mode != ENTRY_MODE_DATA: raise TypeError( "select_item_by_data can only be used in data mode") completion = self._get_entry_completion() model = completion.get_model() for row in model: if row[COL_OBJECT] == data: return row.iter break else: raise KeyError("No item correspond to data %r in the combo %s" % (data, self.name))
[docs] def get_iter_by_label(self, label): completion = self._get_entry_completion() model = completion.get_model() for row in model: if row[COL_TEXT] == label: return row.iter else: raise KeyError("No item correspond to label %r in the combo %s" % (label, self.name))
[docs] def get_selected_by_iter(self, treeiter): completion = self._get_entry_completion() model = completion.get_model() mode = self._mode text = model[treeiter][COL_TEXT] if text != self.get_text(): return if mode == ENTRY_MODE_TEXT: return text elif mode == ENTRY_MODE_DATA: return model[treeiter][COL_OBJECT] else: raise AssertionError
[docs] def get_selected_label(self, treeiter): completion = self._get_entry_completion() model = completion.get_model() return model[treeiter][COL_TEXT]
[docs] def get_selected_data(self, treeiter): completion = self._get_entry_completion() model = completion.get_model() return model[treeiter][COL_OBJECT]
[docs] def get_iter_from_obj(self, obj): mode = self._mode if mode == ENTRY_MODE_TEXT: return self.get_iter_by_label(obj) elif mode == ENTRY_MODE_DATA: return self.get_iter_by_data(obj) else: # XXX: When setting the datatype to non string, automatically go to # data mode raise TypeError("unknown Entry mode. Did you call prefill?")
[docs] def get_mode(self): return self._mode
type_register(KiwiEntry)
[docs]def main(args): win = gtk.Window() win.set_title('gtk.Entry subclass') def cb(window, event): print 'fields', widget.get_field_text() gtk.main_quit() win.connect('delete-event', cb) widget = KiwiEntry() widget.set_mask('000.000.000.000') win.add(widget) win.show_all() widget.select_region(0, 0) gtk.main()
if __name__ == '__main__': import sys sys.exit(main(sys.argv))