2007-01-04 Zsolt Foldvari <zfoldvar@users.sourceforge.net>
* src/GrampsWidgets.py: Add ValidatableMaskedEntry what extends the MaskedEntry with validation feature * TODO: kiwi entry removed svn: r7868
This commit is contained in:
parent
2aac640d7c
commit
259800415a
@ -1,3 +1,8 @@
|
||||
2007-01-04 Zsolt Foldvari <zfoldvar@users.sourceforge.net>
|
||||
* src/GrampsWidgets.py: Add ValidatableMaskedEntry what extends
|
||||
the MaskedEntry with validation feature
|
||||
* TODO: kiwi entry removed
|
||||
|
||||
2007-01-03 Don Allingham <don@gramps-project.org>
|
||||
* src/DataViews/_EventView.py: lint fixes
|
||||
* src/DataViews/_PersonView.py: lint fixes
|
||||
|
6
TODO
6
TODO
@ -12,12 +12,6 @@
|
||||
necessarily need multiple notes. Determine which ones should and
|
||||
shouldn't.
|
||||
|
||||
* Adapt the kiwi-entry widget from the Kiwi project so that GRAMPS can
|
||||
use it. It looks like it can be broken out from the core of the
|
||||
Kiwi project. This would give us filtered input, icons in the entry,
|
||||
and shading of the box. All of which would be good to help restrict
|
||||
input to valid input.
|
||||
|
||||
* Date calculator. See
|
||||
http://sourceforge.net/mailarchive/forum.php?thread_id=3252078&forum_id=1993
|
||||
|
||||
|
@ -24,6 +24,7 @@ import cgi
|
||||
import os
|
||||
import cPickle as pickle
|
||||
from gettext import gettext as _
|
||||
import string
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@ -652,19 +653,142 @@ class PlaceEntry:
|
||||
self.tooltips.set_tip(self.share, _('Select an existing place'))
|
||||
self.tooltips.set_tip(self.add_del, _('Add a new place'))
|
||||
|
||||
##============================================================================##
|
||||
#============================================================================
|
||||
#
|
||||
# MaskedEntry and ValidatableMaskedEntry copied and merged from the Kiwi
|
||||
# project's ValidatableProxyWidgetMixin, KiwiEntry and ProxyEntry.
|
||||
#
|
||||
# http://www.async.com.br/projects/kiwi
|
||||
#
|
||||
#============================================================================
|
||||
|
||||
DEFAULT_DELAY = 500
|
||||
BORDER_WIDTH = 4
|
||||
class MaskError(Exception):
|
||||
pass
|
||||
|
||||
class ValidationError(Exception):
|
||||
pass
|
||||
|
||||
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.
|
||||
"""
|
||||
__gsignals__ = {
|
||||
'done': (gobject.SIGNAL_RUN_FIRST,
|
||||
gobject.TYPE_NONE,
|
||||
()),
|
||||
'color-changed': (gobject.SIGNAL_RUN_FIRST,
|
||||
gobject.TYPE_NONE,
|
||||
(gtk.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))
|
||||
|
||||
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 = gtk.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,
|
||||
gtk.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
|
||||
|
||||
if gtk.pygtk_version < (2,8,0):
|
||||
gobject.type_register(FadeOut)
|
||||
|
||||
class Tooltip(gtk.Window):
|
||||
'''Tooltip for the Icon in the MaskedEntry'''
|
||||
|
||||
DEFAULT_DELAY = 500
|
||||
BORDER_WIDTH = 4
|
||||
|
||||
def __init__(self, widget):
|
||||
gtk.Window.__init__(self, gtk.WINDOW_POPUP)
|
||||
# from gtktooltips.c:gtk_tooltips_force_window
|
||||
self.set_app_paintable(True)
|
||||
self.set_resizable(False)
|
||||
self.set_name("gtk-tooltips")
|
||||
self.set_border_width(BORDER_WIDTH)
|
||||
self.set_border_width(Tooltip.BORDER_WIDTH)
|
||||
self.connect('expose-event', self._on__expose_event)
|
||||
|
||||
self._label = gtk.Label()
|
||||
@ -684,7 +808,7 @@ class Tooltip(gtk.Window):
|
||||
y += widget.allocation.y
|
||||
|
||||
x = screen.get_root_window().get_pointer()[0]
|
||||
x -= (w / 2 + BORDER_WIDTH)
|
||||
x -= (w / 2 + Tooltip.BORDER_WIDTH)
|
||||
|
||||
pointer_screen, px, py, _ = screen.get_display().get_pointer()
|
||||
if pointer_screen != screen:
|
||||
@ -699,11 +823,11 @@ class Tooltip(gtk.Window):
|
||||
elif x < monitor.x:
|
||||
x = monitor.x
|
||||
|
||||
if ((y + h + widget.allocation.height + BORDER_WIDTH) >
|
||||
if ((y + h + widget.allocation.height + Tooltip.BORDER_WIDTH) >
|
||||
monitor.y + monitor.height):
|
||||
y = y - h - BORDER_WIDTH
|
||||
y = y - h - Tooltip.BORDER_WIDTH
|
||||
else:
|
||||
y = y + widget.allocation.height + BORDER_WIDTH
|
||||
y = y + widget.allocation.height + Tooltip.BORDER_WIDTH
|
||||
|
||||
return x, y
|
||||
|
||||
@ -739,13 +863,10 @@ class Tooltip(gtk.Window):
|
||||
if self._show_timeout_id != -1:
|
||||
return
|
||||
|
||||
self._show_timeout_id = gobject.timeout_add(DEFAULT_DELAY,
|
||||
self._show_timeout_id = gobject.timeout_add(Tooltip.DEFAULT_DELAY,
|
||||
self._real_display,
|
||||
widget)
|
||||
|
||||
##============================================================================##
|
||||
##============================================================================##
|
||||
|
||||
# This is tricky and contains quite a few hacks:
|
||||
# An entry contains 2 GdkWindows, one for the background and one for
|
||||
# the text area. The normal one, on which the (normally white) background
|
||||
@ -995,14 +1116,10 @@ class IconEntry(object):
|
||||
else:
|
||||
self._pos = gtk.POS_RIGHT
|
||||
|
||||
##============================================================================##
|
||||
|
||||
import string
|
||||
|
||||
HAVE_2_6 = gtk.pygtk_version[:2] == (2, 6)
|
||||
|
||||
(DIRECTION_LEFT, DIRECTION_RIGHT) = (1, -1)
|
||||
|
||||
|
||||
(INPUT_ASCII_LETTER,
|
||||
INPUT_ALPHA,
|
||||
INPUT_ALPHANUMERIC,
|
||||
@ -1028,32 +1145,19 @@ INPUT_CHAR_MAP = {
|
||||
INPUT_DIGIT: unicode.isdigit,
|
||||
}
|
||||
|
||||
(ENTRY_MODE_UNKNOWN,
|
||||
ENTRY_MODE_TEXT,
|
||||
ENTRY_MODE_DATA) = range(3)
|
||||
|
||||
##def type_register(gtype):
|
||||
##"""Register the type, but only if it's not already registered
|
||||
##@param gtype: the class to register
|
||||
##"""
|
||||
|
||||
### copied from gobjectmodule.c:_wrap_type_register
|
||||
##if (getattr(gtype, '__gtype__', None) !=
|
||||
##getattr(gtype.__base__, '__gtype__', None)):
|
||||
##return False
|
||||
|
||||
##gobject.type_register(gtype)
|
||||
|
||||
##return True
|
||||
(COL_TEXT,
|
||||
COL_OBJECT) = range(2)
|
||||
|
||||
class MaskedEntry(gtk.Entry):
|
||||
"""
|
||||
The MaskedEntry is a Entry subclass with the following additions:
|
||||
|
||||
- Mask, force the input to meet certain requirements
|
||||
- IconEntry, allows you to have an icon inside the entry
|
||||
"""
|
||||
__gtype_name__ = 'MaskedEntry'
|
||||
|
||||
def __init__(self):
|
||||
##self._completion = None
|
||||
|
||||
gtk.Entry.__init__(self)
|
||||
|
||||
self.connect('insert-text', self._on_insert_text)
|
||||
@ -1069,10 +1173,9 @@ class MaskedEntry(gtk.Entry):
|
||||
self.connect('notify::cursor-position',
|
||||
self._on_notify_cursor_position)
|
||||
|
||||
self._completion = None
|
||||
self._exact_completion = False
|
||||
self._block_changed = False
|
||||
|
||||
self._current_object = None
|
||||
##self._mode = ENTRY_MODE_TEXT
|
||||
self._icon = IconEntry(self)
|
||||
|
||||
# List of validators
|
||||
@ -1124,18 +1227,6 @@ class MaskedEntry(gtk.Entry):
|
||||
self._icon.deconstruct()
|
||||
gtk.Entry.do_unrealize(self)
|
||||
|
||||
# Public API
|
||||
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 isinstance(completion, KiwiEntryCompletion):
|
||||
##self.handler_unblock(completion.changed_id)
|
||||
|
||||
# Mask & Fields
|
||||
|
||||
def set_mask(self, mask):
|
||||
@ -1201,7 +1292,9 @@ class MaskedEntry(gtk.Entry):
|
||||
return self._mask
|
||||
|
||||
def get_field_text(self, field):
|
||||
assert self._mask
|
||||
if not self._mask:
|
||||
raise MaskError("a mask must be set before calling get_field_text")
|
||||
#assert self._mask
|
||||
text = self.get_text()
|
||||
start, end = self._mask_fields[field]
|
||||
return text[start: end].strip()
|
||||
@ -1218,7 +1311,9 @@ class MaskedEntry(gtk.Entry):
|
||||
@returns: fields
|
||||
@rtype: list of strings
|
||||
"""
|
||||
assert self._mask
|
||||
if not self._mask:
|
||||
raise MaskError("a mask must be set before calling get_fields")
|
||||
#assert self._mask
|
||||
|
||||
fields = []
|
||||
|
||||
@ -1384,6 +1479,23 @@ class MaskedEntry(gtk.Entry):
|
||||
|
||||
return None
|
||||
|
||||
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_completion()
|
||||
completion.set_match_func(match_func)
|
||||
|
||||
def is_empty(self):
|
||||
text = self.get_text()
|
||||
if self._mask:
|
||||
@ -1427,6 +1539,47 @@ class MaskedEntry(gtk.Entry):
|
||||
|
||||
return True
|
||||
|
||||
def _get_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
|
||||
|
||||
def get_completion(self):
|
||||
return self._completion
|
||||
|
||||
def set_completion(self, completion):
|
||||
gtk.Entry.set_completion(self, completion)
|
||||
# FIXME objects not supported yet, should it be at all?
|
||||
#completion.set_model(gtk.ListStore(str, object))
|
||||
completion.set_model(gtk.ListStore(str))
|
||||
completion.set_text_column(0)
|
||||
self._completion = gtk.Entry.get_completion(self)
|
||||
self.set_exact_completion(self._exact_completion)
|
||||
return
|
||||
|
||||
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
|
||||
|
||||
content = model[iter][COL_TEXT].lower()
|
||||
return key.lower() in content
|
||||
|
||||
def _appers_later(self, char, start):
|
||||
"""
|
||||
Check if a char appers later on the mask. If it does, return
|
||||
@ -1779,32 +1932,326 @@ class MaskedEntry(gtk.Entry):
|
||||
def get_icon_window(self):
|
||||
return self._icon.get_icon_window()
|
||||
|
||||
#type_register(MaskedEntry)
|
||||
gobject.type_register(MaskedEntry)
|
||||
# Combo
|
||||
|
||||
def prefill(self, itemdata, sort=False):
|
||||
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_completion()
|
||||
model = completion.get_model()
|
||||
|
||||
if len(itemdata) == 0:
|
||||
model.clear()
|
||||
return
|
||||
|
||||
values = {}
|
||||
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[item] = None
|
||||
|
||||
model.append((item,))
|
||||
|
||||
if gtk.pygtk_version < (2,8,0):
|
||||
gobject.type_register(MaskedEntry)
|
||||
|
||||
#number = (int, float, long)
|
||||
|
||||
VALIDATION_ICON_WIDTH = 16
|
||||
MANDATORY_ICON = gtk.STOCK_INFO
|
||||
ERROR_ICON = gtk.STOCK_STOP
|
||||
|
||||
class ValidatableMaskedEntry(MaskedEntry):
|
||||
"""It extends the MaskedEntry with validation feature.
|
||||
|
||||
Merged from Kiwi's ValidatableProxyWidgetMixin and ProxyEntry
|
||||
"""
|
||||
|
||||
__gtype_name__ = 'ValidatableMaskedEntry'
|
||||
|
||||
__gsignals__ = {
|
||||
'content-changed': (gobject.SIGNAL_RUN_FIRST,
|
||||
gobject.TYPE_NONE,
|
||||
()),
|
||||
'validation-changed': (gobject.SIGNAL_RUN_FIRST,
|
||||
gobject.TYPE_NONE,
|
||||
(gobject.TYPE_BOOLEAN,)),
|
||||
'validate': (gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_PYOBJECT,
|
||||
(gobject.TYPE_PYOBJECT,)),
|
||||
'changed': 'override',
|
||||
}
|
||||
|
||||
__gproperties__ = {
|
||||
'data-type': (gobject.TYPE_PYOBJECT,
|
||||
'Data Type of the widget',
|
||||
'Type object',
|
||||
gobject.PARAM_READWRITE),
|
||||
'mandatory': (gobject.TYPE_BOOLEAN,
|
||||
'Mandatory',
|
||||
'Mandatory',
|
||||
False,
|
||||
gobject.PARAM_READWRITE),
|
||||
}
|
||||
|
||||
# FIXME put the data type support back
|
||||
#allowed_data_types = (basestring, datetime.date, datetime.time,
|
||||
#datetime.datetime, object) + number
|
||||
|
||||
def __init__(self, data_type=None):
|
||||
self.data_type = None
|
||||
self.mandatory = False
|
||||
|
||||
MaskedEntry.__init__(self)
|
||||
|
||||
self._block_changed = False
|
||||
self._valid = True
|
||||
self._fade = FadeOut(self)
|
||||
self._fade.connect('color-changed', self._on_fadeout__color_changed)
|
||||
|
||||
# FIXME put data type support back
|
||||
#self.set_property('data-type', data_type)
|
||||
|
||||
# Virtual methods
|
||||
def do_changed(self):
|
||||
if self._block_changed:
|
||||
self.emit_stop_by_name('changed')
|
||||
return
|
||||
self.emit('content-changed')
|
||||
self.validate()
|
||||
|
||||
def do_get_property(self, prop):
|
||||
'''Return the gproperty's value.'''
|
||||
|
||||
if prop.name == 'data-type':
|
||||
return self.data_type
|
||||
elif prop.name == 'mandatory':
|
||||
return self.mandatory
|
||||
else:
|
||||
raise AttributeError, 'unknown property %s' % prop.name
|
||||
|
||||
def do_set_property(self, prop, value):
|
||||
'''Set the property of writable properties.'''
|
||||
|
||||
if prop.name == 'data-type':
|
||||
if value is None:
|
||||
self.data_type = value
|
||||
return
|
||||
|
||||
# FIXME put the data type support back
|
||||
#if not issubclass(value, self.allowed_data_types):
|
||||
#raise TypeError(
|
||||
#"%s only accept %s types, not %r"
|
||||
#% (self,
|
||||
#' or '.join([t.__name__ for t in self.allowed_data_types]),
|
||||
#value))
|
||||
self.data_type = value
|
||||
elif prop.name == 'mandatory':
|
||||
self.mandatory = value
|
||||
else:
|
||||
raise AttributeError, 'unknown or read only property %s' % prop.name
|
||||
|
||||
# Public API
|
||||
|
||||
def is_valid(self):
|
||||
"""
|
||||
@returns: True if the widget is in validated state
|
||||
"""
|
||||
return self._valid
|
||||
|
||||
def validate(self, force=False):
|
||||
"""Checks if the data is valid.
|
||||
Validates data-type and custom validation.
|
||||
|
||||
@param force: if True, force validation
|
||||
@returns: validated data or ValueUnset if it failed
|
||||
"""
|
||||
|
||||
# If we're not visible or sensitive return a blank value, except
|
||||
# when forcing the validation
|
||||
if not force and (not self.get_property('visible') or
|
||||
not self.get_property('sensitive')):
|
||||
return None
|
||||
|
||||
try:
|
||||
text = self.get_text()
|
||||
##log.debug('Read %r for %s' % (data, self.model_attribute))
|
||||
|
||||
# check if we should draw the mandatory icon
|
||||
# this need to be done before any data conversion because we
|
||||
# we don't want to end drawing two icons
|
||||
if self.mandatory and self.is_empty():
|
||||
self.set_blank()
|
||||
return None
|
||||
else:
|
||||
if self._completion:
|
||||
for row in self.get_completion().get_model():
|
||||
if row[COL_TEXT] == text:
|
||||
break
|
||||
else:
|
||||
if text:
|
||||
raise ValidationError()
|
||||
else:
|
||||
if not self.is_empty():
|
||||
# this signal calls the on_widgetname__validate method
|
||||
# of the view class and gets the exception (if any).
|
||||
error = self.emit("validate", text)
|
||||
if error:
|
||||
raise error
|
||||
|
||||
self.set_valid()
|
||||
return text
|
||||
except ValidationError, e:
|
||||
self.set_invalid(str(e))
|
||||
return None
|
||||
|
||||
def set_valid(self):
|
||||
"""Changes the validation state to valid, which will remove icons and
|
||||
reset the background color
|
||||
"""
|
||||
|
||||
##log.debug('Setting state for %s to VALID' % self.model_attribute)
|
||||
self._set_valid_state(True)
|
||||
|
||||
self._fade.stop()
|
||||
self.set_pixbuf(None)
|
||||
|
||||
def set_invalid(self, text=None, fade=True):
|
||||
"""Changes the validation state to invalid.
|
||||
@param text: text of tooltip of None
|
||||
@param fade: if we should fade the background
|
||||
"""
|
||||
##log.debug('Setting state for %s to INVALID' % self.model_attribute)
|
||||
|
||||
self._set_valid_state(False)
|
||||
|
||||
# If there is no error text, set a generic one so the error icon
|
||||
# still have a tooltip
|
||||
if not text:
|
||||
text = _("'%s' is not a valid value "
|
||||
"for this field") % self.get_text()
|
||||
|
||||
self.set_tooltip(text)
|
||||
|
||||
if not fade:
|
||||
self.set_stock(ERROR_ICON)
|
||||
self.update_background(gtk.gdk.color_parse(self._fade.ERROR_COLOR))
|
||||
return
|
||||
|
||||
# When the fading animation is finished, set the error icon
|
||||
# We don't need to check if the state is valid, since stop()
|
||||
# (which removes this timeout) is called as soon as the user
|
||||
# types valid data.
|
||||
def done(fadeout, c):
|
||||
self.set_stock(ERROR_ICON)
|
||||
self.queue_draw()
|
||||
fadeout.disconnect(c.signal_id)
|
||||
|
||||
class SignalContainer:
|
||||
pass
|
||||
c = SignalContainer()
|
||||
c.signal_id = self._fade.connect('done', done, c)
|
||||
|
||||
if self._fade.start(self.get_background()):
|
||||
self.set_pixbuf(None)
|
||||
|
||||
def set_blank(self):
|
||||
"""Changes the validation state to blank state, this only applies
|
||||
for mandatory widgets, draw an icon and set a tooltip"""
|
||||
|
||||
##log.debug('Setting state for %s to BLANK' % self.model_attribute)
|
||||
|
||||
if self.mandatory:
|
||||
self.set_stock(MANDATORY_ICON)
|
||||
self.queue_draw()
|
||||
self.set_tooltip(_('This field is mandatory'))
|
||||
self._fade.stop()
|
||||
valid = False
|
||||
else:
|
||||
valid = True
|
||||
|
||||
self._set_valid_state(valid)
|
||||
|
||||
def set_text(self, text):
|
||||
"""
|
||||
Sets the text of the entry
|
||||
|
||||
@param 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
|
||||
MaskedEntry.set_text(self, text)
|
||||
self._block_changed = False
|
||||
self.emit('content-changed')
|
||||
|
||||
self.set_position(-1)
|
||||
|
||||
# Private
|
||||
|
||||
def _set_valid_state(self, state):
|
||||
"""Updates the validation state and emits a signal if it changed"""
|
||||
|
||||
if self._valid == state:
|
||||
return
|
||||
|
||||
self.emit('validation-changed', state)
|
||||
self._valid = state
|
||||
|
||||
# Callbacks
|
||||
|
||||
def _on_fadeout__color_changed(self, fadeout, color):
|
||||
self.update_background(color)
|
||||
|
||||
if gtk.pygtk_version < (2,8,0):
|
||||
gobject.type_register(ValidatableMaskedEntry)
|
||||
|
||||
##============================================================================##
|
||||
|
||||
def main(args):
|
||||
from RelLib import Date
|
||||
from DateHandler import parser
|
||||
|
||||
def on_validate(widget, text):
|
||||
myDate = parser.parse(text)
|
||||
if not myDate.is_regular():
|
||||
return ValidationError("This is not a proper date value")
|
||||
|
||||
win = gtk.Window()
|
||||
win.set_title('gtk.Entry subclass')
|
||||
win.set_title('ValidatableMaskedEntry test window')
|
||||
win.set_position(gtk.WIN_POS_CENTER)
|
||||
def cb(window, event):
|
||||
#print 'fields', widget.get_field_text()
|
||||
gtk.main_quit()
|
||||
win.connect('delete-event', cb)
|
||||
|
||||
widget = MaskedEntry()
|
||||
widget.set_mask('000.000.000.000')
|
||||
vbox = gtk.VBox()
|
||||
win.add(vbox)
|
||||
|
||||
# pixbuf = gtk.gdk.pixbuf_new_from_file("images/stock_lock.png")
|
||||
# widget.set_pixbuf(pixbuf)
|
||||
widget.set_stock(gtk.STOCK_NO)
|
||||
widget.set_tooltip("Tooltip example")
|
||||
|
||||
win.add(widget)
|
||||
label = gtk.Label('Pre-filled entry validated against the given list:')
|
||||
vbox.pack_start(label)
|
||||
|
||||
widget1 = ValidatableMaskedEntry(str)
|
||||
widget1.prefill(('Birth', 'Death', 'Conseption'))
|
||||
vbox.pack_start(widget1, fill=False)
|
||||
|
||||
label = gtk.Label('Mandatory masked entry validated against user function:')
|
||||
vbox.pack_start(label)
|
||||
|
||||
widget2 = ValidatableMaskedEntry(str)
|
||||
widget2.set_mask('00/00/0000')
|
||||
widget2.connect('validate', on_validate)
|
||||
widget2.mandatory = True
|
||||
vbox.pack_start(widget2, fill=False)
|
||||
|
||||
win.show_all()
|
||||
|
||||
widget.select_region(0, 0)
|
||||
gtk.main()
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
Loading…
Reference in New Issue
Block a user