Gtk2Hs has various widgets that can be visually adjusted by the user using the mouse or the keyboard, such as the range widgets, described in the range widget section. There are also a few widgets that display some adjustable portion of a larger area of data, such as the text widget and the viewport widget.
Obviously, an application needs to be able to react to changes the user makes in range widgets. One way to do this would be to have each widget emit its own type of signal when its adjustment changes. But you may also want to connect the adjustments of several widgets together, so that adjusting one adjusts the others. The most obvious example of this is connecting a scrollbar to a panning viewport or a scrolling text area.
The adjustment object can be used to store the configuration parameters and
values of range widgets, such as scrollbars and scale controls. Because
Adjustment
is derived from GObject
and
Object
, adjustments can emit signals, which can be used not only
to allow your program to react to user input on adjustable widgets, but also to
propagate adjustment values transparently between adjustable widgets.
Many of the widgets which use adjustment objects, like
ScrolledWindow
, can create their own adjustments, but you create
one yourself with:
adjustmentNew :: Double -- value - The initial value of the range -> Double -- lower - The minimum value of the range -> Double -- upper - The maximum value of the range -> Double -- stepIncrement - The smaller of two possible increments -> Double -- pageIncrement - The larger of two possible increments -> Double -- pageSize - The size of the visible area -> IO Adjustment
The creation function takes every value that is contained in the object:
value
is the initial value and should be between the
upper
and lower
bounds of the slider. Clicking on the
arrows increases this value by stepIncrement
. Clicking in the
slider advances by pageIncrement
. The pageSize
is
needed to determine if the end of the slider is still in the range. You can get
and set all the parameters of an adjustment by methods or using the general
set
and get
functions on the adjustment attributes.
onValueChanged :: Adjustment -> IO () -> IO (ConnectId Adjustment)
is the signal emitted when the value of the adjustment changes, and:
onAdjChanged :: Adjustment -> IO () -> IO (ConnectId Adjustment)
is the signal emitted when one or more of the other than the value fields have changed.
Scale widgets are used to allow the user to visually select and manipulate a value within a specific range using a slider. You might want to use a scale widget, for example, to adjust the magnification level on a zoomed preview of a picture, or to control the brightness of a color, or to specify the number of minutes of inactivity before a screensaver takes over the screen.
The following functions create vertical and horizontal scale widgets, respectively:
vScaleNew :: Adjustment -> IO VScale hScaleNew :: Adjustment -> IO Hscale
There are also two constructors which do not take an adjustment:
vScaleNewWithRange :: Double ->. Double -> Double -> IO VScale hScaleNewWithRange :: Double ->. Double -> Double -> IO Hscale
The Double
parameters refer to the minimum and maximum values and
the step. The step increment (preferably a power of 10) is the value the scale
moves when the arrow keys are used.
Horizontal and vertical scales are instances of ScaleClass
and
their common behaviors are defined in the module
Graphics.UI.Gtk.Abstract.Scale
.
Scale widgets can display their current value as a number beside the trough. The default behaviour is to show the value, but you can change this with this function:
scaleSetDrawValue :: ScaleClass self => self -> Bool -> IO ()
The value displayed by a scale widget is rounded to one decimal point by
default, as is the value field in its Adjustment
. You
can change this with:
scaleSetDigits :: ScaleClass self => self -> Int -> IO ()
Finally, the value can be drawn in different positions relative to the trough:
scaleSetValuePos :: ScaleClass self => self -> PositionType -> IO ()
The PositionType
is defined as:
data PositionType = PosLeft | PosRight | PosTop | PosBottom
Scale
itself inherits many methods form its base class, which is
Range
.
The update policy of a range widget defines at what points during user
interaction it will change the value field of its
Adjustment
and emit the onRangeValueChanged
signal
on this Adjustment
. The update policies are defined by the
UpdateType
, which has three constructors:
UpdateContinuous
onRangeValueChanged
signal is emitted
continuously, i.e., whenever the slider is moved by even the tiniest
amount.
UpdateDiscontinuous
onRangeValueChanged
signal is only emitted once the slider
has stopped moving and the user has released the mouse button.
UpdateDelayed
onRangeValueChanged
signal is emitted when the user releases
the mouse button, or if the slider stops moving for a short period of time.
The update policy of a range widget can be set by:
rangeSetUpdatePolicy :: RangeClass self => self -> UpdateType -> IO ()
Getting and setting the adjustment for a range widget on the fly is done, predictably, with:
rangeGetAdjustment :: RangeClass self => self -> IO Adjustment rangeSetAdjustment :: RangeClass self => self -> Adjustment -> IO ()
rangeSetAdjustment
does absolutely nothing if you pass it the
adjustment that it is already using, regardless of whether you changed
any of its fields or not. If you pass it a new Adjustment
, it will
unreference the old one if it exists (possibly destroying it), connect the
appropriate signals to the new one, and call the private function
gtk_range_adjustment_changed()
, which will (or at least, is
supposed to...) recalculate the size and/or position of the slider and redraw
if necessary. As mentioned in the section on adjustments, if you wish to reuse
the same Adjustment
, when you modify its values directly, you
should emit the changed
signal on it.
All of the Gtk2Hs range widgets react to mouse clicks in more or less the same
way. Clicking mouse button 1 in the trough will cause its adjustment's
stepIncrement
to be added or subtracted from its value,
and the slider to be moved accordingly. Clicking mouse button 2 in the trough
will jump the slider to the point at which the button was clicked. Clicking
mouse button 3 in the trough of a range or any button on a scrollbar's arrows
will cause its adjustment's value to change by stepIncrement
at a
time.
Note: This did not work on Linux Fedora 6 with the standard mouse bindings.
Scrollbars are not focusable, thus have no key bindings. The key bindings for the other range widgets (which are, of course, only active when the widget has focus) do not differentiate between horizontal and vertical range widgets.
All range widgets can be operated with the left, right, up and down arrow keys,
as well as with the Page Up and Page Down
keys. The arrows move the slider up and down by stepIncrement
,
while Page Up and Page Down move it by
pageIncrement
. Home and End move
to the beginning and end of the slider.
The user can also move the slider all the way to one end or the other of the trough using the keyboard. This is done with the Home and End keys.
This example puts up a window with three range widgets all connected to the same adjustment, and a couple of controls for adjusting some of the parameters mentioned above so you can see how they affect the way these widgets work for the user.
The three scales are placed so the vertical is next to the two horizontal ones,
one above the other. So we need a horizontal box for the vertical scale and a
vertical box next to it for the horizontal scales. The scales and the boxes
must be packed with PackGrow
so the scales will resize with the
main box, which is a vertical box in the window.
All three scales ar constructed with the same adjustment, setting the initial value at 0.0, the minimum value at 0.0, the maximum value at 101.0, the step increment at 0.1, the page increment at 1.0 and the page size at 1.0.
adj1 <- adjustmentNew 0.0 0.0 101.0 0.1 1.0 1.0
The user can control whether the scale values are displayed with a
checkButton
. This is packed into the main box and set to be active
initially. A check button is a toggle button and when the user checks or
uncehecks it the onToggled
signal is sent. this causes the
toggleDisplay
function to be evaluated, which is defined as:
toggleDisplay :: ScaleClass self => CheckButton -> [self] -> IO () toggleDisplay b scls = sequence_ (map change scls) where change sc = do st <- toggleButtonGetActive b scaleSetDrawValue sc st
The function has a checkButton
type as its parameter, and a list
of instances of ScaleClass
. However, a list can only contain
values of the same type, and vScale
and hScale
are
different types. So, we can use the function on lists of vertical scales or
horizontal scales, but lists containing both types result in a typing error.
The user can select the positionType
using a widget not mentioned
before, a ComboBox
. This allows a selection of choices as shown
below. The one to be set active is determined by an index, which is 0 here, the
first one.
makeOpt1 :: IO ComboBox makeOpt1 = do cb <- comboBoxNewText comboBoxAppendText cb "TOP" comboBoxAppendText cb "BOTTOM" comboBoxAppendText cb "LEFT" comboBoxAppendText cb "RIGHT" comboBoxSetActive cb 0 return cb
A second comboBox
lets the user select the update policy, one of
the three UpdateType
constructors.
makeOpt2 :: IO ComboBox makeOpt2 = do cb <- comboBoxNewText comboBoxAppendText cb "Continuous" comboBoxAppendText cb "Discontinuous" comboBoxAppendText cb "Delayed" comboBoxSetActive cb 0 return cb
The combo boxes themselves just display text, of course. To select the position, respectively the update policy, we define:
setScalePos :: ScaleClass self => ComboBox -> self -> IO () setScalePos cb sc = do ntxt <- comboBoxGetActiveText cb let pos = case ntxt of (Just "TOP") -> PosTop (Just "BOTTOM") -> PosBottom (Just "LEFT") -> PosLeft (Just "RIGHT") -> PosRight Nothing -> error "setScalePos: no position set" scaleSetValuePos sc pos setUpdatePol :: RangeClass self => ComboBox -> self -> IO () setUpdatePol cb sc = do ntxt <- comboBoxGetActiveText cb let pol = case ntxt of (Just "Continuous") -> UpdateContinuous (Just "Discontinuous") -> UpdateDiscontinuous (Just "Delayed") -> UpdateDelayed Nothing -> error "setUpdatePol: no policy set" rangeSetUpdatePolicy sc pol
Here we have not used lists to manage the vertical and horizontal scales, so each horizontal scale is addressed separately.
The number of precision shown on the three scales will be managed with another scale, for which we use a new adjustment. The maximum precision is 10 and each increment is 1. The precision of this control scale itself is set to 1.
adj2 <- adjustmentNew 1.0 0.0 5.0 1.0 1.0 0.0
When the control adjustment changes, the signal onValueChanged
will
be emitted and then the defined function setDigits
is evaluated.
setDigits :: ScaleClass self => self -> Adjustment -> IO () setDigits sc adj = do val <- get adj adjustmentValue set sc [scaleDigits := (round val)]
Here we use the general functions set
and get
on the
attributes; we might have used the appropriate methods as well. Note that the
Double
of the adjustment value must be rounded to an
Integral
type.
We use another horizontal scale to manage the page size of the three example
scales. When set at 0.0 the scales can reach their initial maximum of 100.0 and
when set at 100.0 the scales are fixed at the lowest value. This involves the
adjusting of the adjustment by a onValueChanged
signal from a
third adjustment by this code snippet:
onValueChanged adj3 $ do val <- adjustmentGetValue adj3 adjustmentSetPageSize adj1 val
The main function is:
import Graphics.UI.Gtk main :: IO () main = do initGUI window <- windowNew set window [windowTitle := "range controls", windowDefaultWidth := 250] mainbox <- vBoxNew False 10 containerAdd window mainbox containerSetBorderWidth mainbox 10 box1 <- hBoxNew False 0 boxPackStart mainbox box1 PackGrow 0 adj1 <- adjustmentNew 0.0 0.0 101.0 0.1 1.0 1.0 vsc <- vScaleNew adj1 boxPackStart box1 vsc PackGrow 0 box2 <- vBoxNew False 0 boxPackStart box1 box2 PackGrow 0 hsc1 <- hScaleNew adj1 boxPackStart box2 hsc1 PackGrow 0 hsc2 <- hScaleNew adj1 boxPackStart box2 hsc2 PackGrow 0 chb <- checkButtonNewWithLabel "Display Value on Scale Widgets" boxPackStart mainbox chb PackNatural 10 toggleButtonSetActive chb True box3 <- hBoxNew False 10 boxPackStart mainbox box3 PackNatural 0 label1 <- labelNew (Just "Scale Value Position:") boxPackStart box3 label1 PackNatural 0 opt1 <- makeOpt1 boxPackStart box3 opt1 PackNatural 0 box4 <- hBoxNew False 10 boxPackStart mainbox box4 PackNatural 0 label2 <- labelNew (Just "Scale Update Policy:") boxPackStart box4 label2 PackNatural 0 opt2 <- makeOpt2 boxPackStart box4 opt2 PackNatural 0 adj2 <- adjustmentNew 1.0 0.0 5.0 1.0 1.0 0.0 box5 <- hBoxNew False 0 containerSetBorderWidth box5 10 boxPackStart mainbox box5 PackGrow 0 label3 <- labelNew (Just "Scale Digits:") boxPackStart box5 label3 PackNatural 10 dsc <- hScaleNew adj2 boxPackStart box5 dsc PackGrow 0 scaleSetDigits dsc 0 adj3 <- adjustmentNew 1.0 1.0 101.0 1.0 1.0 0.0 box6 <- hBoxNew False 0 containerSetBorderWidth box6 10 boxPackStart mainbox box6 PackGrow 0 label4 <- labelNew (Just "Scrollbar Page Size:") boxPackStart box6 label4 PackNatural 10 psc <- hScaleNew adj3 boxPackStart box6 psc PackGrow 0 scaleSetDigits psc 0 onToggled chb $ do toggleDisplay chb [hsc1,hsc2] toggleDisplay chb [vsc] onChanged opt1 $ do setScalePos opt1 hsc1 setScalePos opt1 hsc2 setScalePos opt1 vsc onChanged opt2 $ do setUpdatePol opt2 hsc1 setUpdatePol opt2 hsc2 setUpdatePol opt2 vsc onValueChanged adj2 $ do setDigits hsc1 adj2 setDigits hsc2 adj2 setDigits vsc adj2 onValueChanged adj3 $ do val <- adjustmentGetValue adj3 adjustmentSetPageSize adj1 val widgetShowAll window onDestroy window mainQuit mainGUI
The non standard functions used in the listing have already been listed above.