@c -----------------------------------------------------------------------------
@c File     : gtk-range-widgets.texi
@c License  : GNU General Public License (GPL)
@c Language : English
@c Author   : Dieter Kaiser
@c Date     : 09.03.2012
@c Revision : 01.08.2013
@c
@c Copyright (C) 2012, 2013 by Dieter Kaiser
@c -----------------------------------------------------------------------------

@menu
* Introduction to Range Widgets::
* Scrollbar Widgets::
* Scale Widgets::
* Common Range Functions::
* Example Range Widgets::
@end menu

@c -----------------------------------------------------------------------------
@node Introduction to Range Widgets, Scrollbar Widgets, Top, Top
@section Introduction to Range Widgets

The category of range widgets includes the ubiquitous scrollbar widget and the
less common scale widget.  Though these two types of widgets are generally used
for different purposes, they are quite similar in function and implementation.
All range widgets share a set of common graphic elements, each of which has its
own X window and receives events.  They all contain a "trough" and a "slider"
(what is sometimes called a "thumbwheel" in other GUI environments).  Dragging
the slider with the pointer moves it back and forth within the trough, while
clicking in the trough advances the slider towards the location of the click,
either completely, or by a designated amount, depending on which mouse button
is used.

As mentioned in @ref{Adjustments}, all range widgets are associated with
an adjustment object, from which they calculate the length of the slider and
its position within the trough.  When the user manipulates the slider, the range
widget will change the value of the adjustment.

@c -----------------------------------------------------------------------------
@node Scrollbar Widgets, Scale Widgets, Introduction to Range Widgets, Top
@section Scrollbar Widgets

@findex GtkScrollbar
@findex gtk-scrollbar
@findex scrollbar, gtk-scrollbar

@findex gtk-scrollbar-new
@findex scrollbar-new, gtk-scrollbar-new

These are your standard, run-of-the-mill scrollbars.  These should be used only
for scrolling some other widget, such as a list, a text box, or a viewport (and
it is generally easier to use the scrolled window widget in most cases).  For
other purposes, you should use scale widgets, as they are friendlier and more
featureful.

The @xurl{gtk-scrollbar} widget is a horizontal or vertical scrollbar, depending
on the value of the @code{orientation} property.  A scrollbar can be created
with the function @xurl{gtk-scrollbar-new}, which takes two arguments.  The
first argument gives the direction @code{:horizontal} or @code{:vertical} and
the second a @xurl{gtk-adjustment}.  The second argument can be @code{NIL}.
In this case a @xurl{gtk-adjustment} is created.

The position of the thumb in a scrollbar is controlled by the scroll
adjustments.  See @xurl{gtk-adjustment} for the fields in an adjustment - for
@xurl{gtk-scrollbar}, the property @code{value} of @xurl{gtk-adjustment}
represents the position of the scrollbar, which must be between the
@code{lower} and @code{(upper - page-size)} properties.  The property
@code{page-size} represents the size of the visible scrollable area.  The
properties @code{step-increment} and @code{page-increment} are used when the
user asks to step down (using the small stepper arrows) or page down (using for
example the PageDown key).

@emph{Note}

The @xurl{gtk-hscrollbar} widget for a horizontal scrollbar and the
@xurl{gtk-vscrollbar} widget for a vertical scrollbar are deprecated, but
present in GTK+ 3.6.  Furthermore, the deprecated functions
@xurl{gtk-hscrollbar-new} and @xurl{gtk-vscrollbar-new} are availabe in
GTK+ 3.6, but these functions return a @xurl{gtk-scrollbar} widget and not a
@code{gtk-hscrollbar} or @code{gtk-vscrollbar} widget.

@c -----------------------------------------------------------------------------
@node Scale Widgets, Common Range Functions, Scrollbar Widgets, Top
@section Scale Widgets

@findex GtkScale
@findex gtk-scale
@findex scale, gtk-scale

@findex gtk-scale-new
@findex gtk-scale-new-with-range

@findex scale-new, gtk-scale-new
@findex scale-new-with-range, gtk-scale-new-with-range

Scale widgets are used to allow the user to visually select and manipulate a
value within a specific range.  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.

As with scrollbars, the @xurl{gtk-scale} widget is a horizontal or vertical
scale, depending on the value of the @code{orientation} property.  A scale can
be created with the function @xurl{gtk-scale-new}, which takes two arguments.
The first argument gives the direction @code{:horizontal} or @code{:vertical}
and the second a @xurl{gtk-adjustment}.

The adjustment argument can either be an adjustment which has already been
created with @xurl{gtk-adjustment-new}, or @code{NIL}, in which case, an
anonymous adjustment is created with all of its values set to 0.0 (which
is not very useful in this case).  In order to avoid confusing yourself, you
probably want to create your adjustment with a @code{page-size} of 0.0 so
that its upper value actually corresponds to the highest value the user can
select.  The function @xurl{gtk-scale-new-with-range} variant take care of
creating a suitable adjustment.  The function takes four arguments.  The first
argument is again the orientation of the scale and the next arguments represent
the values for creating an @xurl{gtk-adjustment} with intial values for the
properties @code{lower}, @code{upper}, and @code{step-increment}.  If you are
already thoroughly confused, read the section @ref{Adjustments} again for an
explanation of what exactly adjustments do and how to create and manipulate
them.

@c -----------------------------------------------------------------------------
@subheading Functions and Signals

@findex GtkPositionType
@findex gtk-position-type
@findex position-type, gtk-position-type

@findex gtk-scale-draw-value
@findex gtk-scale-set-digits
@findex gtk-scale-value-pos

@findex scale-draw-value, gtk-scale-draw-value
@findex scale-set-digits, gtk-scale-set-digits
@findex scale-value-pos, gtk-scale-value-pos

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
with the generic function @xurl{gtk-scale-draw-value}, which takes as the first
argument a widget of type @xurl{gtk-scale} and as the second argument
@code{draw-value}, which is either @code{T} or @code{NIL}, with predictable
consequences for either one.

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 the
function @xurl{gtk-scale-set-digits}.  The first argument is a widget of type
@xurl{gtk-scale} and the second argument @code{digits}, where @code{digits} is
the number of decimal places you want.  You can set digits to anything you like,
but no more than 13 decimal places will actually be drawn on screen.

Finally, the value can be drawn in different positions relative to the trough
with the generic function @xurl{gtk-scale-value-pos}.  The first argument is
again a scale widget.  The second argument @code{pos} is of the enumeration type
@xurl{gtk-position-type}.

If you position the value on the "side" of the trough (e. g., on the top or
bottom of a horizontal scale widget), then it will follow the slider up and
down the trough.

@c -----------------------------------------------------------------------------
@node Common Range Functions, Example Range Widgets, Scale Widgets, Top
@section Common Range Functions

The range widget class is fairly complicated internally, but, like all the
base class widgets, most of its complexity is only interesting if you want to
hack on it.  Also, almost all of the functions and signals it defines are only
really used in writing derived widgets.  There are, however, a few useful
functions that will work on all range widgets.

@subheading Getting and Setting Adjustments

@findex gtk-range-adjustment
@findex gtk-range-adjustment-changed

@findex range-adjustment, gtk-range-adjustment
@findex range-adjustment-changed, gtk-range-adjustment-changed

Getting and setting the adjustment for a range widget "on the fly" is done with
the function @xurl{gtk-range-adjustment}.  The function
@xurl{gtk-range-adjustment} returns the adjustment to which the range is
connected.

@xurl{gtk-range-adjustment} does absolutely nothing if you pass it the
adjustment that range 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 emit the "changed" signal, which will recalculate the size
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.

@c -----------------------------------------------------------------------------
@subheading Key and Mouse bindings

All of the GTK+ range widgets react to mouse clicks in more or less the same
way.  Clicking button-1 in the trough will cause its adjustment's
@code{page-increment} 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 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 @code{step-increment} at a time.

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) are 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 @code{step-increment}, while Page Up and Page Down move it by
@code{page-increment}.

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.

@c -----------------------------------------------------------------------------
@node Example Range Widgets, , Common Range Functions, Top
@section Example Range Widgets

@ifnotinfo
@float Figure, figure-range-widgets
@caption{Range Widgets}
@center @image{figures/range-widgets269x420, 179pt}
@end float
@end ifnotinfo

@ref{example-range-widgets} is a somewhat modified version of the
"range controls" test from @file{testgtk.c}.  It basically 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 and in the section
on adjustments, so you can see how they affect the way these widgets work for
the user.

You will notice that the program does not call @xurl{g-signal-connect} for the
"delete-event", but only for the "destroy" signal. This will still perform the
desired function, because an unhandled "delete-event" will result in a "destroy"
signal being given to the window.

@float Example, example-range-widgets
@caption{Range Widgets}
@end float
@verbatim
(defun example-range-widgets ()
  (within-main-loop
    (let* ((window (make-instance 'gtk-window
                                  :type :toplevel
                                  :title "Example Range Widgets"))
           (box1 (make-instance 'gtk-box
                                :orientation :vertical
                                :homogeneous nil
                                :spacing 0))
           (box2 (make-instance 'gtk-box
                                :orientation :horizontal
                                :homogeneous nil
                                :spacing 12
                                :border-width 12))
           (box3 (make-instance 'gtk-box
                                :orientation :vertical
                                :homogeneous nil
                                :spacing 12))
           (adj1 (make-instance 'gtk-adjustment
                                :value 0.0
                                :lower 0.0
                                :upper 101.0
                                :step-increment 0.1
                                :page-increment 1.0
                                :page-size 1.0))
           (vscale (make-instance 'gtk-scale
                                  :orientation :vertical
                                  :digits 1
                                  :value-pos :top
                                  :draw-value t
                                  :adjustment adj1))
           (hscale (make-instance 'gtk-scale
                                   :orientation :horizontal
                                   :digits 1
                                   :value-pos :top
                                   :draw-value t
                                   :width-request 200
                                   :height-request -1
                                   :adjustment adj1))
           (scrollbar (make-instance 'gtk-scrollbar
                                     :orientation :horizontal
                                     :adjustment adj1)))
      ;; Connect a handler for the signal "destroy" to the main window.
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      ;; Packing of the global widgets hscale, vscale, and scrollbar
      (gtk-container-add window box1)
      (gtk-box-pack-start box1 box2)
      (gtk-box-pack-start box2 vscale)
      (gtk-box-pack-start box2 box3)
      (gtk-box-pack-start box3 hscale)
      (gtk-box-pack-start box3 scrollbar)
      ;; A check button to control whether the value is displayed or not.
      (let ((box (make-instance 'gtk-box
                                :orientation :horizontal
                                :homogeneous nil
                                :spacing 12
                                :border-width 12))
            (button (make-instance 'gtk-check-button
                                   :label "Display value on scale widget"
                                   :active t)))
        (g-signal-connect button "toggled"
           (lambda (widget)
             (setf (gtk-scale-draw-value hscale)
                   (gtk-toggle-button-active widget))
             (setf (gtk-scale-draw-value vscale)
                   (gtk-toggle-button-active widget))))
        (gtk-box-pack-start box button)
        (gtk-box-pack-start box1 box))
      ;; A ComboBox to change the position of the value.
      (let ((box (make-instance 'gtk-box
                                :orientation :horizontal
                                :homogeneous nil
                                :spacing 12
                                :border-width 12))
            (combo (make-instance 'gtk-combo-box-text)))
        (gtk-combo-box-text-append-text combo "TOP")
        (gtk-combo-box-text-append-text combo "BOTTOM")
        (gtk-combo-box-text-append-text combo "LEFT")
        (gtk-combo-box-text-append-text combo "RIGHT")
        (gtk-combo-box-set-active combo 0)
        (g-signal-connect combo "changed"
           (lambda (widget)
             (let ((pos (gtk-combo-box-text-get-active-text widget)))
               (format t "type      : ~A~%"
                         (g-type-from-instance (pointer widget)))
               (format t "active is : ~A~%"
                         (gtk-combo-box-get-active widget))
               (setq pos (if pos (intern pos :keyword) :top))
               (setf (gtk-scale-value-pos hscale) pos)
               (setf (gtk-scale-value-pos vscale) pos))))
        (gtk-box-pack-start box
                            (make-instance 'gtk-label
                                           :label "Scale value position")
                            :expand nil :fill nil :padding 0)
        (gtk-box-pack-start box combo)
        (gtk-box-pack-start box1 box))
      ;; Create a scale to change the digits of hscale and vscale.
      (let* ((box (make-instance 'gtk-box
                                 :orientation :horizontal
                                 :homogeneous nil
                                 :spacing 12
                                 :border-width 12))
             (adj (make-instance 'gtk-adjustment
                                 :value 1.0
                                 :lower 0.0
                                 :upper 5.0
                                 :step-increment 1.0
                                 :page-increment 1.0
                                 :page-size 0.0))
             (scale (make-instance 'gtk-scale
                                   :orientation :horizontal
                                   :digits 0
                                   :adjustment adj)))
        (g-signal-connect adj "value-changed"
           (lambda (adjustment)
             (setf (gtk-scale-digits hscale)
                   (truncate (gtk-adjustment-value adjustment)))
             (setf (gtk-scale-digits vscale)
                   (truncate (gtk-adjustment-value adjustment)))))
        (gtk-box-pack-start box
                            (make-instance 'gtk-label
                                           :label "Scale Digits:")
                            :expand nil :fill nil)
        (gtk-box-pack-start box scale)
        (gtk-box-pack-start box1 box))
      ;; Another hscale for adjusting the page size of the scrollbar
      (let* ((box (make-instance 'gtk-box
                                 :orientation :horizontal
                                 :homogeneous nil
                                 :spacing 12
                                 :border-width 12))
             (adj (make-instance 'gtk-adjustment
                                 :value 1.0
                                 :lower 1.0
                                 :upper 101.0
                                 :step-increment 1.0
                                 :page-increment 1.0
                                 :page-size 0.0))
             (scale (make-instance 'gtk-scale
                                   :orientation :horizontal
                                   :digits 0
                                   :adjustment adj)))
        (g-signal-connect adj "value-changed"
           (lambda (adjustment)
             (setf (gtk-adjustment-page-size adj1)
                   (gtk-adjustment-page-size adjustment))
             (setf (gtk-adjustment-page-increment adj1)
                   (gtk-adjustment-page-increment adjustment))))
        (gtk-box-pack-start box
                            (make-instance 'gtk-label
                                           :label "Scrollbar Page Size:")
                            :expand nil :fill nil)
        (gtk-box-pack-start box scale)
        (gtk-box-pack-start box1 box))
      ;; Add a separator
      (gtk-box-pack-start box1
                          (make-instance 'gtk-separator
                                         :orientation :horizontal)
                          :expand nil :fill t)
      ;; Create the quit button.
      (let ((box (make-instance 'gtk-box
                                :orientation :vertical
                                :homogeneous nil
                                :spacing 12
                                :border-width 12))
            (button (make-instance 'gtk-button :label "Quit")))
        (g-signal-connect button "clicked"
                          (lambda (button)
                            (declare (ignore button))
                            (gtk-widget-destroy window)))
        (gtk-box-pack-start box button)
        (gtk-box-pack-start box1 box :expand nil))
      (gtk-widget-show-all window))))
@end verbatim

