[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

TkEntry validation?

Matt Maycock

11/11/2006 7:21:00 PM

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(c)
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

4 Answers

Matt Maycock

11/11/2006 7:27:00 PM

0

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!


On 11/11/06, Matt Maycock <ummaycoc@gmail.com> wrote:
> 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(c)
> 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
>


--
There's no word in the English language for what you do to a dead
thing to make it stop chasing you.

Matt Maycock

11/11/2006 7:36:00 PM

0

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(c)
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... :-)

Hidetoshi NAGAI

11/12/2006 1:31:00 AM

0

Matt Maycock

11/13/2006 2:18:00 AM

0

> 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!