From: Matt Maycock Date: 2006-11-12T04:26:32+09:00 Subject: Re: TkEntry validation? 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 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.