Buttons are easy to create, as we have seen in the last installment of
this series. What about more complex widgets? Both Xmms and Winamp® feature
a nice volume control. Is this possible with tcl code too?
Of course! This time I will show you how to do this. Let's look at the bitmap
for the several volume level states of the control:
The image have two main sections. The first section is an array of images
for the background of the control, for a quantized number of volume settings.
The second section have the slider button in two states, idle or while dragging
the control. The background images moreover, are 28, what means we must approximate
to one of two, when doing a fine adjust at the actual volume level.
The code to import those bitmaps in our tcl program is very easy. Each bitmap
is 15 pixels high, so we may write a loop to place each slice in a tk's image
object, which we will name is0, is1, ... is27.
set steps 28Now we need to capture the slider images, as well. The code is not much more difficult. We just have to take care of the offsets and determinate
set sw [image width isc]
set sh 15
for {set i 0} {$i < $steps} {incr i} {
set img is${i}
image create photo $img
$img copy isc -from 0 [expr $i * $sh] \
$sw [expr ($i+1) * $sh - 2]
}
image create photo ib0
image create photo ib1
ib0 copy isc -from 0 [expr $steps * $sh + 2 ] 14 [expr $steps * $sh + 13]
ib1 copy isc -from 15 [expr $steps * $sh + 2 ] 28 [expr $steps * $sh + 13]
Now we need to display both images, one for the background of the controle,
and the other for the slider. As we want the slider displayed over the background,
the choice of geometry managers will be the placer. Then we create
a container frame and place both images as labels, the slider image over
the background.
frame .bts -width 100 -height 50
label .lb0 -image is0 -highlightthickness 0 -border 0
label .lb1 -image ib1 -highlightthickness 0 -border 0
place .lb0 -x 0 -y 0 -in .bts
place .lb1 -x 30 -y 1 -in .bts
pack .bts -padx 5 -pady 15
Here is how it looks like, just after being displayed:
Now, look at the same widget, as we move the slider:

Notice that, in the top image, the first background is being displayed. The
slider gets its first bitmap, or ib1, because the other will show
only when the mouse grabs it. The next step now is to define the bindings,
so it will behave as expected. There are two kind of bindings we need. First,
binding for the slider motion, so we set our background bitmap reflecting
its position change, and also place the slider in a new location when the
mouse buttons is released. The other binding is for when the user clicks
outside of the slider, in the background directly, which should result in
a quick change of the volume.
Let's look at the bindings for everything:
bind .lb1 <1> {+
set x %X
.lb1 config -image ib0
}
bind .lb1 <ButtonRelease-1> {+
.lb1 config -image ib1
}
bind .lb1 <B1-Motion> {+
set x1 %X
array set a [place info .lb1]
set newx [expr $a(-x) + $x1 - $x]
set maxx [expr $sw - 15]
if {$newx > $maxx} {
set newx $maxx
}
if {$newx < 0} {
set newx 0
}
set k [expr $newx * $steps / [winfo width .lb0]]
.lb0 config -image is$k
place .lb1 -x $newx
set x $x1
}
bind .lb0 <1> {+
#puts "changed %W -> %x %y"
set k [expr %x * $steps / [winfo width .lb0]]
.lb0 config -image is$k
set newx %x
set maxx [expr $sw - 16]
if {%x > $maxx} {
set newx $maxx
}
place .lb1 -x $newx -anchor nw
}
Ok, this is not practical to use as a widget yet, but I hope you got the
idea. Some suggestions to change this short tutorial in a real world widget
are, first change all the local variables into array components or regular
namespace variables, where each instance of the widget have its own private
variables. Second, when changing the slider position, store the (possibly
scaled) value of its new position in a user defined variable for the official
position (like -variable in regular tk's scale widget). This
is done near the end of .lb1 <B1-Motion> binding. Third, create
code for transparent creation and destruction of the widget.
You may get the full source code of this volume control here, its volume.bmp image file, and while you are still here, the source for our previous skinable button tutorial.
That's all fellows. Happy hacking!