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