Hi.
I wrote the following to make some of my input validation from
TkEntry’s a bit easier. I thought some others might be interested
(still has some parts that could use work)
Usage:
num = TkEntry.new(…)
num.filter.integer.nosign # 123, 625, 86434, etc
year = TkEntry.new(…)
year.filter.integer.nosign.left_digit(4) # any number up to four
digits - no sign
float = TkEntry.new(…)
float.filter.real.right_digit(3) # any real number with at most 3
places past the decimal
month = TkEntry.new(…)
month.filter.month.letters(3) # A month - can be either 1-12 or
names (up to 3 letters)
require ‘tk’
class TkEntry
def acceptable_keys
# This part is probably what needs more info in it…
%w{Tab ISO_Left_Tab Left Right Shift Shift_L Shift_R}
end
def removal_keys
%w{BackSpace Delete}
end
def month_names
%w{january february march april may june july august september
october november december}
end
def generic_remove(key)
s = self.value.dup
if self.selection_present then
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = “”
elsif key == “BackSpace” && self.cursor > 0 then
s[self.cursor - 1, 1] = “”
elsif key == “Delete” && self.cursor < s.length
s[self.cursor, 1] = “”
end
s
end
def generic_insert(key)
return generic_remove(key) if removal_keys.include?(key)
s = self.value.dup
if self.selection_present then
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = key
else
s.insert(self.cursor, key)
end
s
end
def generic_number_insert(key)
return generic_remove(key) if removal_keys.include?(key)
s = self.value.dup
if key =~ /^\d$/ then
if self.selection_present then
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = key
else
s.insert(self.cursor, key)
end
elsif %w{minus plus}.include?(key) then
v = {‘minus’ => ‘-’, ‘plus’ => ‘+’}[key]
if self.selection_present then
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = v
else
s.insert(self.cursor, v)
end
elsif key == ‘period’
if self.selection_present then
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = ‘.’
else
s.insert(self.cursor, ‘.’)
end
else
Tk.callback_break # Not something to be used with numbers?
end
s
end
def filter
me, f = self, Object.new
f.instance_eval {
@filter_target = me;
}
def f.method_missing(sym, *args, &block)
if [:positive, :negative, :nosign, :integer, :real, :left_digit,
:right_digit, :range, :month, :letters, :alphanumeric].include?(sym)
then
@filter_target.key_filter([sym, *args])
self
else
super(sym, *args, &block)
end
end
f
end
def key_filter_proc(type, args)
case type
when :positive
Proc.new {|key|
next if self.acceptable_keys.include?(key)
s = self.generic_number_insert(key)
Tk.callback_break if s =~ /^.++/ || s =~ /+.+/
Tk.callback_break if s =~ /-/
Tk.callback_break if s =~ /^+?0/
}
when :negative
Proc.new {|key|
next if self.acceptable_keys.include?(key)
s = self.generic_number_insert(key)
Tk.callback_break if s =~ /^.±/ || s =~ /-.-/
Tk.callback_break if s =~ /+/
Tk.callback_break unless s.length == 0 || s =~ /^-/
Tk.callback_break if s =~ /^-0/
}
when :nosign
Proc.new {|key|
next if self.acceptable_keys.include?(key)
Tk.callback_break if %w{plus minus}.include?(key)
}
when :integer
Proc.new {|key|
next if self.acceptable_keys.include?(key)
s = self.generic_number_insert(key)
Tk.callback_break if s =~ /^(-|+)?0\d/
Tk.callback_break if s =~ /^.+(-|+)/
Tk.callback_break if s =~ /./
}
when :real
Proc.new {|key|
next if self.acceptable_keys.include?(key)
s = self.generic_number_insert(key)
Tk.callback_break if s =~ /^(-|+)?0\d/
Tk.callback_break if s =~ /^.+(-|+)/
Tk.callback_break if s.scan(/./).length > 1
}
when :left_digit
raise(ArgumentError, “Need a number of digits to allow on the
left of the decimal”) if args.empty?
n = args[0].to_i
Proc.new {|key|
next if acceptable_keys.include?(key)
s = self.generic_number_insert(key)
Tk.callback_break if s =~ /^(-|+)?(\d)/ && $2.length > n
}
when :right_digit
raise(ArgumentError, “Need a number of digits to allow on the
left of the decimal”) if args.empty?
n = args[0].to_i
Proc.new {|key|
next if acceptable_keys.include?(key)
s = self.generic_number_insert(key)
Tk.callback_break if s =~ /.(\d+)$/ && $1.length > n
}
when :range
raise(ArgumentError, “Need a range to test against”) if
args.length < 1
r = (args[0].to_i)…(args[1].to_i)
Proc.new {|key|
next if acceptable_keys.include?(key)
s = self.generic_number_insert(key)
Tk.callback_break if s.to_f < r.begin || s.to_f > r.end
}
when :month
Proc.new {|key|
next if acceptable_keys.include?(key)
Tk.callback_break unless key =~ /^(\d|\w)$/
s = self.generic_insert(key)
Tk.callback_break if s =~ /\d/ && s =~ /[A-Za-z]/
if s =~ /^\d+$/ then
Tk.callback_break unless (1..12).include?(s.to_i)
else
match = Regexp.compile("^#{s}", true)
Tk.callback_break unless self.month_names.any? {|i| i =~
match}
end
}
when :letters
if args.empty? then
Proc.new {|key|
next if acceptable_keys.include?(key)
Tk.callback_break unless key =~ /^[A-Za-z]$/
}
else
n = args[0].to_i
Proc.new {|key|
next if acceptable_keys.include?(key)
Tk.callback_break unless key =~ /^[A-Za-z]$/ &&
generic_insert(key).length <= n
}
end
when :alphanumeric
if args.empty? then
Proc.new {|key|
next if acceptable_keys.include?(key)
Tk.callback_break unless key =~ /^\w$/
}
else
Proc.new {|key|
next if acceptable_keys.include?(key)
next if key =~ /^\w$/
next if args.include?(key)
Tk.callback_break
}
end
else
raise(ArgumentError, “Could not determine type of filter.”)
end
end
def key_filter(*constraints)
constraints.each {|c|
self.bind_append(‘Key’, case c
# Allow only positive numbers (but only checks that we have
only plus sign and only one in the front
when Symbol then
key_filter_proc©
when Array # Special Cases
raise(ArgumentError, “Array based filters cannot be empty.”)
if c.empty?
raise(ArgumentError, “Array based filters must start with a
symbol.”) unless Symbol === c[0]
key_filter_proc(*c)
end, ‘%K’)
}
end
end
def Tk.datetime_filter
[fyr, fdy, fhr, fmi, fsc].each {|i|
i.key_filter(:integer, :nosign)
}
fyr.key_filter([:left_digit, 4])
fmo.key_filter(:month, [:letters, 3])
fdy.key_filter([:range, 1, 31])
fhr.key_filter([:range, 0, 23])
[fmi, fsc].each {|i|
i.key_filter([:range, 0, 59])
}
nil
end