class Counter(Pmw
.MegaWidget
):
def __init__(self
, parent
= None, **kw
):
# Define the megawidget options.
('buttonaspect', 1.0, INITOPT
),
('datatype', 'numeric', self
._datatype
),
('labelmargin', 0, INITOPT
),
('labelpos', None, INITOPT
),
('orient', 'horizontal', INITOPT
),
('repeatrate', 50, None),
('sticky', 'ew', INITOPT
),
self
.defineoptions(kw
, optiondefs
)
# Initialise the base class (after defining the options).
Pmw
.MegaWidget
.__init
__(self
, parent
)
# Initialise instance variables.
self
._normalRelief
= None
interior
= self
.interior()
# If there is no label, put the arrows and the entry directly
# into the interior, otherwise create a frame for them. In
# either case the border around the arrows and the entry will
# be raised (but not around the label).
if self
['labelpos'] is None:
if not kw
.has_key('hull_relief'):
frame
.configure(relief
= 'raised')
if not kw
.has_key('hull_borderwidth'):
frame
.configure(borderwidth
= 1)
frame
= self
.createcomponent('frame',
Tkinter
.Frame
, (interior
,),
relief
= 'raised', borderwidth
= 1)
frame
.grid(column
=2, row
=2, sticky
=self
['sticky'])
interior
.grid_columnconfigure(2, weight
=1)
interior
.grid_rowconfigure(2, weight
=1)
self
._downArrowBtn
= self
.createcomponent('downarrow',
Tkinter
.Canvas
, (frame
,),
width
= 16, height
= 16, relief
= 'raised', borderwidth
= 2)
# Create the entry field.
self
._counterEntry
= self
.createcomponent('entryfield',
(('entry', 'entryfield_entry'),), None,
Pmw
.EntryField
, (frame
,))
self
._upArrowBtn
= self
.createcomponent('uparrow',
Tkinter
.Canvas
, (frame
,),
width
= 16, height
= 16, relief
= 'raised', borderwidth
= 2)
if orient
== 'horizontal':
self
._downArrowBtn
.grid(column
= 0, row
= 0)
self
._counterEntry
.grid(column
= 1, row
= 0,
self
._upArrowBtn
.grid(column
= 2, row
= 0)
frame
.grid_columnconfigure(1, weight
= 1)
frame
.grid_rowconfigure(0, weight
= 1)
if Tkinter
.TkVersion
>= 4.2:
frame
.grid_columnconfigure(0, pad
= padx
)
frame
.grid_columnconfigure(2, pad
= padx
)
frame
.grid_rowconfigure(0, pad
= pady
)
elif orient
== 'vertical':
self
._upArrowBtn
.grid(column
= 0, row
= 0, sticky
= 's')
self
._counterEntry
.grid(column
= 0, row
= 1,
self
._downArrowBtn
.grid(column
= 0, row
= 2, sticky
= 'n')
frame
.grid_columnconfigure(0, weight
= 1)
frame
.grid_rowconfigure(0, weight
= 1)
frame
.grid_rowconfigure(2, weight
= 1)
if Tkinter
.TkVersion
>= 4.2:
frame
.grid_rowconfigure(0, pad
= pady
)
frame
.grid_rowconfigure(2, pad
= pady
)
frame
.grid_columnconfigure(0, pad
= padx
)
raise ValueError, 'bad orient option ' + repr(orient
) + \
': must be either \'horizontal\' or \'vertical\''
self
.createlabel(interior
)
self
._upArrowBtn
.bind('<Configure>', self
._drawUpArrow
)
self
._upArrowBtn
.bind('<1>', self
._countUp
)
self
._upArrowBtn
.bind('<Any-ButtonRelease-1>', self
._stopCounting
)
self
._downArrowBtn
.bind('<Configure>', self
._drawDownArrow
)
self
._downArrowBtn
.bind('<1>', self
._countDown
)
self
._downArrowBtn
.bind('<Any-ButtonRelease-1>', self
._stopCounting
)
self
._counterEntry
.bind('<Configure>', self
._resizeArrow
)
entry
= self
._counterEntry
.component('entry')
entry
.bind('<Down>', lambda event
, s
= self
: s
._key
_decrement
(event
))
entry
.bind('<Up>', lambda event
, s
= self
: s
._key
_increment
(event
))
# Need to cancel the timer if an arrow button is unmapped (eg:
# its toplevel window is withdrawn) while the mouse button is
# held down. The canvas will not get the ButtonRelease event
# if it is not mapped, since the implicit grab is cancelled.
self
._upArrowBtn
.bind('<Unmap>', self
._stopCounting
)
self
._downArrowBtn
.bind('<Unmap>', self
._stopCounting
)
# Check keywords and initialise options.
def _resizeArrow(self
, event
):
for btn
in (self
._upArrowBtn
, self
._downArrowBtn
):
bw
= (string
.atoi(btn
['borderwidth']) +
string
.atoi(btn
['highlightthickness']))
newHeight
= self
._counterEntry
.winfo_reqheight() - 2 * bw
newWidth
= int(newHeight
* self
['buttonaspect'])
btn
.configure(width
=newWidth
, height
=newHeight
)
def _drawUpArrow(self
, event
):
self
._drawArrow
(self
._upArrowBtn
)
def _drawDownArrow(self
, event
):
self
._drawArrow
(self
._downArrowBtn
)
def _drawArrow(self
, arrow
):
if self
['orient'] == 'vertical':
if arrow
== self
._upArrowBtn
:
if arrow
== self
._upArrowBtn
:
Pmw
.drawarrow(arrow
, self
['entry_foreground'], direction
, 'arrow')
def _stopCounting(self
, event
= None):
if self
._timerId
is not None:
self
.after_cancel(self
._timerId
)
if self
._normalRelief
is not None:
button
, relief
= self
._normalRelief
button
.configure(relief
=relief
)
self
._normalRelief
= None
def _countUp(self
, event
):
self
._normalRelief
= (self
._upArrowBtn
, self
._upArrowBtn
.cget('relief'))
self
._upArrowBtn
.configure(relief
='sunken')
# Force arrow down (it may come up immediately, if increment fails).
self
._upArrowBtn
.update_idletasks()
def _countDown(self
, event
):
self
._normalRelief
= (self
._downArrowBtn
, self
._downArrowBtn
.cget('relief'))
self
._downArrowBtn
.configure(relief
='sunken')
# Force arrow down (it may come up immediately, if increment fails).
self
._downArrowBtn
.update_idletasks()
def _key_increment(self
, event
):
def _key_decrement(self
, event
):
datatype
= self
['datatype']
if type(datatype
) is types
.DictionaryType
:
self
._counterArgs
= datatype
.copy()
if self
._counterArgs
.has_key('counter'):
datatype
= self
._counterArgs
['counter']
del self
._counterArgs
['counter']
if _counterCommands
.has_key(datatype
):
self
._counterCommand
= _counterCommands
[datatype
]
self
._counterCommand
= datatype
validValues
= _counterCommands
.keys()
raise ValueError, ('bad datatype value "%s": must be a' +
' function or one of %s') % (datatype
, validValues
)
def _forceCount(self
, factor
):
text
= self
._counterEntry
.get()
value
= apply(self
._counterCommand
,
(text
, factor
, self
['increment']), self
._counterArgs
)
previousICursor
= self
._counterEntry
.index('insert')
if self
._counterEntry
.setentry(value
) == Pmw
.OK
:
self
._counterEntry
.xview('end')
self
._counterEntry
.icursor(previousICursor
)
def _count(self
, factor
, first
):
origtext
= self
._counterEntry
.get()
value
= apply(self
._counterCommand
,
(origtext
, factor
, self
['increment']), self
._counterArgs
)
# If text is invalid, stop counting.
# If incrementing produces an invalid value, restore previous
# text and stop counting.
previousICursor
= self
._counterEntry
.index('insert')
valid
= self
._counterEntry
.setentry(value
)
self
._counterEntry
.setentry(origtext
)
self
._counterEntry
.xview('end')
self
._counterEntry
.icursor(previousICursor
)
delay
= self
['repeatrate']
self
._timerId
= self
.after(delay
,
lambda self
=self
, factor
=factor
: self
._count
(factor
, 0))
Pmw
.MegaWidget
.destroy(self
)
Pmw
.forwardmethods(Counter
, Pmw
.EntryField
, '_counterEntry')
def _changeNumber(text
, factor
, increment
):
value
= string
.atol(text
)
value
= (value
/ increment
) * increment
+ increment
value
= ((value
- 1) / increment
) * increment
# Get rid of the 'L' at the end of longs (in python up to 1.5.2).
def _changeReal(text
, factor
, increment
, separator
= '.'):
value
= Pmw
.stringtoreal(text
, separator
)
# Compare reals using str() to avoid problems caused by binary
# numbers being only approximations to decimal numbers.
# For example, if value is -0.3 and increment is 0.1, then
# int(value/increment) = -2, not -3 as one would expect.
if str(div
)[-2:] == '.0':
# value is an even multiple of increment.
div
= round(div
) + factor
# value is not an even multiple of increment.
index
= string
.find(text
, '.')
text
= text
[:index
] + separator
+ text
[index
+ 1:]
def _changeDate(value
, factor
, increment
, format
= 'ymd',
separator
= '/', yyyy
= 0):
jdn
= Pmw
.datestringtojdn(value
, format
, separator
) + factor
* increment
y
, m
, d
= Pmw
.jdntoymd(jdn
)
result
= result
+ separator
result
= result
+ '%02d' % y
result
= result
+ '%02d' % (y
% 100)
result
= result
+ '%02d' % m
result
= result
+ '%02d' % d
_SECSPERDAY
= 24 * 60 * 60
def _changeTime(value
, factor
, increment
, separator
= ':', time24
= 0):
unixTime
= Pmw
.timestringtoseconds(value
, separator
)
chunks
= unixTime
/ increment
+ 1
chunks
= (unixTime
- 1) / increment
unixTime
= chunks
* increment
unixTime
= unixTime
+ _SECSPERDAY
while unixTime
>= _SECSPERDAY
:
unixTime
= unixTime
- _SECSPERDAY
return '%s%02d%s%02d%s%02d' % (sign
, hours
, separator
, mins
, separator
, secs
)
# hexadecimal, alphabetic, alphanumeric not implemented
'numeric' : _changeNumber
, # } integer
'integer' : _changeNumber
, # } these two use the same function
'real' : _changeReal
, # real number