TkEntry validation?

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

actually, update ~ line 154 from

Tk.callback_break unless key =~ /^(\d|\w)$/

to
Tk.callback_break unless removal_keys.include?(key) || key =~
/^(\d|\w)$/

(Sorry, I just added the functionality of dealing with backspace /
delete correctly, and forgot that part).

~Matthew Maycock!

and…

I also forgot to add that part back into the letters / alphanumerics
(before this, it just passively ignored backspace/delete as an
`acceptable_key’ instead of testing the new result, sorry for posting
buggyness).

`semi-final’ version of the code

require ‘tk’

class TkEntry
def acceptable_keys
%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 removal_keys.include?(key) || 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)
next if removal_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)
next if removal_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) ||
removal_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

Note - unless other people post, this is going to look really

pathetic with me having 3 posts in a row, 2 showing how trigger happy
I am to post… :slight_smile:

Why don’t you use ‘validatecommand’ (or ‘vcmd’) option and on?
Of course I know that API of validation of entry widgets is very
basic. But I think that you can make some part of your code simple.

If you don’t see the option, please read ‘VALIDATION’ part of the
Tcl/Tk’s manual of entry widgets. You’ll be able to see examples of
its usage in “ext/tk/sample/demos-en/entry3.rb” on Ruby’s source tree.

When I originally went looking into validate, I found it hard to find
documentation - but that example code was very helpful. Thank you.

~Matthew Maycock!

From: “Matt Maycock” [email protected]
Subject: TkEntry validation?
Date: Sun, 12 Nov 2006 04:20:49 +0900
Message-ID:
[email protected]

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)

Why don’t you use ‘validatecommand’ (or ‘vcmd’) option and on?
Of course I know that API of validation of entry widgets is very
basic. But I think that you can make some part of your code simple.

If you don’t see the option, please read ‘VALIDATION’ part of the
Tcl/Tk’s manual of entry widgets. You’ll be able to see examples of
its usage in “ext/tk/sample/demos-en/entry3.rb” on Ruby’s source tree.