1# entry3.rb -- 2# 3# This demonstration script creates several entry widgets whose 4# permitted input is constrained in some way. It also shows off a 5# password entry. 6# 7# based on Tcl/Tk8.4.4 widget demos 8 9if defined?($entry3_demo) && $entry3_demo 10 $entry3_demo.destroy 11 $entry3_demo = nil 12end 13 14$entry3_demo = TkToplevel.new {|w| 15 title("Constrained Entry Demonstration") 16 iconname("entry3") 17 positionWindow(w) 18} 19 20base_frame = TkFrame.new($entry3_demo).pack(:fill=>:both, :expand=>true) 21 22TkLabel.new(base_frame, 23 :font=>$font, :wraplength=>'5i', :justify=>:left, 24 :text=><<EOL).pack(:side=>:top) 25Four different entries are displayed below. You can add characters \ 26by pointing, clicking and typing, though each is constrained in what \ 27it will accept. The first only accepts integers or the empty string \ 28(checking when focus leaves it) and will flash to indicate any \ 29problem. The second only accepts strings with fewer than ten \ 30characters and sounds the bell when an attempt to go over the limit \ 31is made. The third accepts US phone numbers, mapping letters to \ 32their digit equivalent and sounding the bell on encountering an \ 33invalid character or if trying to type over a character that is not \ 34a digit. The fourth is a password field that accepts up to eight \ 35characters (silently ignoring further ones), and displaying them as \ 36asterisk characters. 37EOL 38 39TkFrame.new(base_frame){|f| 40 pack(:side=>:bottom, :fill=>:x, :pady=>'2m') 41 42 TkButton.new(f, :text=>'Dismiss', :width=>15, :command=>proc{ 43 $entry3_demo.destroy 44 $entry3_demo = nil 45 }).pack(:side=>:left, :expand=>true) 46 47 TkButton.new(f, :text=>'See Code', :width=>15, :command=>proc{ 48 showCode 'entry3' 49 }).pack(:side=>:left, :expand=>true) 50} 51 52# focusAndFlash -- 53# Error handler for entry widgets that forces the focus onto the 54# widget and makes the widget flash by exchanging the foreground and 55# background colours at intervals of 200ms (i.e. at approximately 56# 2.5Hz). 57# 58# Arguments: 59# widget - entry widget to flash 60# fg - Initial foreground colour 61# bg - Initial background colour 62# count - Counter to control the number of times flashed 63def focusAndFlash(widget, fg, bg, count=5) 64 return if count <= 0 65 if fg && !fg.empty? && bg && !bg.empty? 66 TkTimer.new(200, count, 67 proc{widget.configure(:foreground=>bg, :background=>fg)}, 68 proc{widget.configure(:foreground=>fg, :background=>bg)} 69 ).start 70 else 71 # TkTimer.new(150, 3){Tk.bell}.start 72 Tk.bell 73 TkTimer.new(200, count, 74 proc{widget.configure(:foreground=>'white', 75 :background=>'black')}, 76 proc{widget.configure(:foreground=>'black', 77 :background=>'white')} 78 ).at_end{begin 79 widget.configure(:foreground=>fg, 80 :background=>bg) 81 rescue 82 # ignore 83 end}.start 84 end 85 widget.focus(true) 86end 87 88l1 = TkLabelFrame.new(base_frame, :text=>"Integer Entry") 89TkEntry.new(l1, :validate=>:focus, 90 :vcmd=>[ 91 proc{|s| s == '' || /^[+-]?\d+$/ =~ s }, '%P' 92 ]) {|e| 93 fg = e.foreground 94 bg = e.background 95 invalidcommand [proc{|w| focusAndFlash(w, fg, bg)}, '%W'] 96 pack(:fill=>:x, :expand=>true, :padx=>'1m', :pady=>'1m') 97} 98 99l2 = TkLabelFrame.new(base_frame, :text=>"Length-Constrained Entry") 100TkEntry.new(l2, :validate=>:key, :invcmd=>proc{Tk.bell}, 101 :vcmd=>[proc{|s| s.length < 10}, '%P'] 102 ).pack(:fill=>:x, :expand=>true, :padx=>'1m', :pady=>'1m') 103 104### PHONE NUMBER ENTRY ### 105# Note that the source to this is quite a bit longer as the behaviour 106# demonstrated is a lot more ambitious than with the others. 107 108# Initial content for the third entry widget 109entry3content = TkVariable.new("1-(000)-000-0000") 110 111# Mapping from alphabetic characters to numbers. 112$phoneNumberMap = {} 113Hash[*(%w(abc 2 def 3 ghi 4 jkl 5 mno 6 pqrs 7 tuv 8 wxyz 9))].each{|chars, n| 114 chars.split('').each{|c| 115 $phoneNumberMap[c] = n 116 $phoneNumberMap[c.upcase] = n 117 } 118} 119 120# phoneSkipLeft -- 121# Skip over fixed characters in a phone-number string when moving left. 122# 123# Arguments: 124# widget - The entry widget containing the phone-number. 125def phoneSkipLeft(widget) 126 idx = widget.index('insert') 127 if idx == 8 128 # Skip back two extra characters 129 widget.cursor = idx - 2 130 elsif idx == 7 || idx == 12 131 # Skip back one extra character 132 widget.cursor = idx - 1 133 elsif idx <= 3 134 # Can't move any further 135 Tk.bell 136 Tk.callback_break 137 end 138end 139 140# phoneSkipRight -- 141# Skip over fixed characters in a phone-number string when moving right. 142# 143# Arguments: 144# widget - The entry widget containing the phone-number. 145# add - Offset to add to index before calculation (used by validation.) 146def phoneSkipRight(widget, add = 0) 147 idx = widget.index('insert') 148 if (idx + add == 5) 149 # Skip forward two extra characters 150 widget.cursor = idx + 2 151 elsif (idx + add == 6 || idx + add == 10) 152 # Skip forward one extra character 153 widget.cursor = idx + 1 154 elsif (idx + add == 15 && add == 0) 155 # Can't move any further 156 Tk.bell 157 Tk.callback_break 158 end 159end 160 161# validatePhoneChange -- 162# Checks that the replacement (mapped to a digit) of the given 163# character in an entry widget at the given position will leave a 164# valid phone number in the widget. 165# 166# widget - entry widget to validate 167# vmode - The widget's validation mode 168# idx - The index where replacement is to occur 169# char - The character (or string, though that will always be 170# refused) to be overwritten at that point. 171 172def validatePhoneChange(widget, vmode, idx, char) 173 return true if idx == nil 174 Tk.after_idle(proc{widget.configure(:validate=>vmode, 175 :invcmd=>proc{Tk.bell})}) 176 if !(idx<3 || idx==6 || idx==7 || idx==11 || idx>15) && char =~ /[0-9A-Za-z]/ 177 widget.delete(idx) 178 widget.insert(idx, $phoneNumberMap[char] || char) 179 Tk.after_idle(proc{phoneSkipRight(widget, -1)}) 180 return true 181 # Tk.update(true) # <- Don't work 'update' inter validation callback. 182 # It depends on Tcl/Tk side (tested on Tcl/Tk8.5a1). 183 end 184 return false 185end 186 187 188l3 = TkLabelFrame.new(base_frame, :text=>"US Phone-Number Entry") 189TkEntry.new(l3, :validate=>:key, :invcmd=>proc{Tk.bell}, 190 :textvariable=>entry3content, 191 :vcmd=>[ 192 proc{|w,v,i,s| validatePhoneChange(w,v,i,s)}, 193 "%W %v %i %S" 194 ]){|e| 195 # Click to focus goes to the first editable character... 196 bind('FocusIn', proc{|d,w| 197 if d != "NotifyAncestor" 198 w.cursor = 3 199 Tk.after_idle(proc{w.selection_clear}) 200 end 201 }, '%d %W') 202 bind('Left', proc{|w| phoneSkipLeft(w)}, '%W') 203 bind('Right', proc{|w| phoneSkipRight(w)}, '%W') 204 pack(:fill=>:x, :expand=>true, :padx=>'1m', :pady=>'1m') 205} 206 207l4 = TkLabelFrame.new(base_frame, :text=>"Password Entry") 208TkEntry.new(l4, :validate=>:key, :show=>'*', 209 :vcmd=>[ 210 proc{|s| s.length <= 8}, 211 '%P' 212 ]).pack(:fill=>:x, :expand=>true, :padx=>'1m', :pady=>'1m') 213 214TkFrame.new(base_frame){|f| 215 lower 216 TkGrid.configure(l1, l2, :in=>f, :padx=>'3m', :pady=>'1m', :sticky=>:ew) 217 TkGrid.configure(l3, l4, :in=>f, :padx=>'3m', :pady=>'1m', :sticky=>:ew) 218 TkGrid.columnconfigure(f, [0,1], :uniform=>1) 219 pack(:fill=>:both, :expand=>true) 220} 221