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