Tutoriel Gtk2Hs

4.2 Réglages, curseurs, plages de valeurs

Gtk2Hs a différents widgets qui peuvent être ajustés visuellement par l’utilisateur en utilisant la souris ou le clavier tel que le widget curseur (slider) décris dans la section correspondante. Il y a aussi quelques widgets qui affichent certaines portions d’une plus grande quantité de données telles le widget text et le widget viewport.

Évidemment, une application a besoin de réagir aux changements que l’utilisateur effectue dans le widget slider. Une des solutions serait que chaque widget émette son propre type de signal quand les réglages ont été faits. Mais vous pouvez aussi vouloir connecter les réglages de plusieurs widgets ensemble de sorte que la modification de l’un modifie l’autre. L’exemple le plus évident est de connecter une barre de défilement à un viewport ou une zone de texte.

L’objet Adjustment peut être utilisé pour stocker les paramètres de configuration et les valeurs des widgets réglables, tels que des barres de défilement ou de réglage. Parce que Adjustment dérive de GObject et Object, les objets Adjustment peuvent émettre des signaux qui peuvent être utilisés non seulement pour permettre au programme de réagir aux réglages de l’utilisateur, mais aussi de propager les valeurs du réglage de manière transparente entre les widgets ajustables.

Plusieurs des widgets qui utilisent les objets Adjustment, comme ScrolledWindow peuvent créer leur propre environnement, mais vous pouvez créer le vôtre avec:

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

La fonction de création prend toutes les valeurs qui sont contenus dans l’objet: value est la valeur initiale et doit se trouver entre les limites upper et lower du slider. Cliquer sur les flèches augmente la valeur de stepIncrement. Cliquer sur le slider avance de pageIncrement. pageSize est requis pour déterminer si la fin du slider est toujours dans les limites. Vous pouvez définir et obtenir tous les attributs d’un réglage par les méthodes spécifiques ou bien en utilisant les fonctions set et get:

onValueChanged :: Adjustment -> IO () -> IO (ConnectId Adjustment)

est le signal émis lorsque la valeur ou le réglage change et:

onAdjChanged :: Adjustment -> IO () -> IO (ConnectId Adjustment)

est le signal émis lorsque un ou plusieurs autres champs actuels sont modifiés.

Widgets échelle

Les widgets échelle sont utilisés pour permettre à l’utilisateur de sélectionner et manipuler visuellement une valeur dans une plage de valeur en utilisant un curseur. Vous pouvez utiliser un widget de ce type pour être utilisé , par exemple,pour ajuster le grossissement d’une image, contrôler la brillance de la couleur ou spécifier le nombre de minutes d’inactivité avant que l’économiseur d’écran ne se mette en route.

Les fonctions suivantes créent respectivement un widget curseur vertical et horizontal:

vScaleNew :: Adjustment -> IO VScale

hScaleNew :: Adjustment -> IO Hscale

Il y a également deux constructeurs qui ne prennent pas de réglage.

vScaleNewWithRange :: Double ->. Double -> Double -> IO VScale

hScaleNewWithRange :: Double ->. Double -> Double -> IO Hscale

Les paramètres Double contiennent les valeurs minimum, maximum et l’incrément. L’incrément est la valeur dont l’échelle est modifiée lorsque les flèches sont utilisées.

Les échelles horizontales et verticales sont des instances de ScaleClass et leur comportements communs sont définis dans le module Graphics.UI.Gtk.Abstract.Scale.

Les widgets échelle peuvent afficher leur valeur courante en tant que nombre juste à coté. Le comportement par défaut est de montrer la valeur, mais vous pouvez changer cela avec la fonction:

scaleSetDrawValue :: ScaleClass self => self -> Bool -> IO ()

La valeur affichée par un widget échelle est arrondie par défaut à une décimale. Vous pouvez changer cela avec:

scaleSetDigits :: ScaleClass self => self -> Int -> IO ()

Au final, la valeur peut être afficher à différentes positions:

scaleSetValuePos :: ScaleClass self => self -> PositionType -> IO ()

Le type de PositionType est définie comme ceci:

data PositionType = PosLeft | PosRight | PosTop | PosBottom

Scale hérite lui-même de plusieurs méthodes de sa classe de base qui est Range.

Définition de la politique de mise à jour

La politique de mise à jour d’un widget définit à quel moment, pendant la saisie de l’utilisateur, le champ value va être modifié et émettre le signal onRangeValueChanged. Les politiques de mise à jour sont définies UpdateType qui a trois constructeurs:

UpdateContinuous
C’est le constructeur par défaut. Le signal onRangeValueChanged est émis en continu à chaque mouvement de slider même de la valeur la plus infime. UpdateDiscontinuous
Le signal onRangeValueChanged est émis seulement lorsque l’utilisateur a arrêté de bouger le slider et a relâche le bouton. UpdateDelayed
Le signal onRangeValueChanged est émis lorsque l’utilisateur relâche le bouton de la souris ou si le slider n’est plus bougé pendant un certain laps de temps.

La politique de mise à jour du widget peut être définie avec:

rangeSetUpdatePolicy :: RangeClass self => self -> UpdateType -> IO ()

Définir et accéder aux réglages

Obtenir et fixer les réglages d’un widget échelle peut se faire avec:

rangeGetAdjustment :: RangeClass self => self -> IO Adjustment

rangeSetAdjustment :: RangeClass self => self -> Adjustment -> IO ()

rangeSetAdjustment ne fait absolument rien si vous lui passez le Adjustment qui est déjà en cours d’utilisation sans vérifier si vous avez changé certains des champs ou non. Si vous passez une nouveau Adjustment, il va déréférencer l’ancien, connecter les signaux appropriés au nouveau et appeler la fonction privée gtk_range_adjustment_changed() qui va (ou du moins est supposée) recalculer la taille ou la position du slider et le redessiner si nécessaire. Comme mentioné dans la section des Adjustment, si vous souhaitez réutiliser le même Adjustment quand vous modifiez ses valeurs directement, vous devez émettre le signal changed dessus.

Liens entre touches et souris

Tous les widgets échelle de Gtk2Hs réagissent aux clics souris plus ou moins de la même façon. Cliquer sur le bouton 1 dessus va ajouter ou soustraire au réglage le stepIncrement de la valeur value, et le slider sera bougé en accord. Cliquer sur bouton 2 va mettre le slider au point sur lequel le bouton a été cliqué.

Les barres de défilement ne sont pas et n’ont pas de touches associées. Les touches associées aux autres widgets échelle (qui sont, bien sur, seulement actives lorsque le widget a le focus) ne font pas de distinctions entre les widgets échelle verticaux et horizontaux.

Tous les widgets échelle peuvent être actionnés avec les flèches, gauche, droite, haute et basse aussi bien qu’avec les touches Page Up et Page Down. Les flèches bougent le slider en haut et en bas par le stepIncrement, alors que Page Up et Page Down le modifie par pageIncrement. Les touches Home et End positionnent le curseur au tout début ou tout à la fin.

Exemple

Cet exemple met en place une fenêtre avec trois widgets échelle tous connectés au même réglage, et un couple de contrôle pour ajuster certains des paramètres mentionnés ci-dessus, vous pourrez alors voir comment ils affectent le fonctionnement des widgets pour l’utilisateur.

Gtk Gtk2Hs Range widgets example

Les trois échelles sont placées de telle sorte que celui qui est vertical est à coté des deux qui sont à l’horizontale. Nous avons donc besoin d’une boîte horizontale pour l’échelle verticale et d’une boite verticale à coté pour les deux échelles horizontales. Les échelles et les boites doivent être empaquetées avec PackGrow de sorte que les échelles soient redimensionnées avec la boite principale qui est elle même une boite verticale dans la fenêtre.

Les trois échelles sont construites avec le même Adjustment, définissant la valeur initiale à 0.0, la valeur minimale à 0.0, la valeur maximale à 101.0, le pas stepIncrement à 0.1, le pas pageIncrement à 1.0 et la taille de page à 1.0.

adj1 <- adjustmentNew 0.0 0.0 101.0 0.1 1.0 1.0

L’utilisateur peut controler si les valeurs de l’échelle sont affichées avec un checkButton. Il est empaqueté dans la boite principale et activé par défaut. Un checkButton est un bouton bascule et quand l’utilisateur le coche ou le décoche le signal onToggled est émis. Cela déclenche l’évaluation de la fonction toggleDisplay, qui est définie :

toggleDisplay :: ScaleClass self => CheckButton -> [self] -> IO ()
toggleDisplay b scls = sequence_ (map change scls) where
                         change sc = do st <- toggleButtonGetActive b
                                        scaleSetDrawValue sc st

La fonction prend un type checkButton comme paramètre et une liste de ScaleClass. Cependant, une liste peut seulement contenir des valeurs du même type, et vScale et hScale sont de types différents. Nous pouvons donc utiliser la fonction sur des listes d’échelles verticales ou sur des listes d’échelles horizontales, mais les listes contenant les deux types engendreront une erreur de typage.

L’utilisateur peut sélectionner positionType en utilisant un widget que nous n’avons pas encore abordé, une ComboBox. Elles permettent une sélection de choix. Celui qui doit être actif est déterminé par un index, qui est 0 ici (c’est à dire le premier):

makeOpt1 :: IO ComboBox
makeOpt1 = do
  cb <- comboBoxNewText
  comboBoxAppendText cb "TOP"
  comboBoxAppendText cb "BOTTOM"
  comboBoxAppendText cb "LEFT"
  comboBoxAppendText cb "RIGHT"
  comboBoxSetActive cb 0
  return cb

Une seconde comboBox laisse l’utilisateur sélectionner la politique de mise à jour avec les trois constructeurs UpdateType:

makeOpt2 :: IO ComboBox
makeOpt2 = do
  cb <- comboBoxNewText
  comboBoxAppendText cb "Continuous"
  comboBoxAppendText cb "Discontinuous"
  comboBoxAppendText cb "Delayed"
  comboBoxSetActive cb 0
  return cb

Les comboBox en elle même ne font qu’afficher du texte. Pour sélectionner la position et la politique de mise à jour, on définit

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

Nous n’avons pas utilisé de listes pour gérer les échelles verticales et horizontales, de sorte que chaque échelle horizontale est gérée séparément.

La précision du nombre affiché sur les trois échelles sera ajusté par une autre échelle pour laquelle nous utilisons un autre réglage. La précision maximum est 10 est chaque incrément est de 1. La précision de cette échelle de contrôle est de 1.

  adj2 <- adjustmentNew 1.0 0.0 5.0 1.0 1.0 0.0

Quand le réglage est modifié, le signal onValueChanged est émis et la fonction définie setDigits est évaluée.

setDigits :: ScaleClass self => self -> Adjustment -> IO ()
setDigits sc adj = do val <- get adj adjustmentValue
                      set sc [scaleDigits := (round val)]

Nous utilisons ici les fonctions générales set et get sur les attributs; mais nous pourrions tout aussi bien utiliser les méthodes appropriées. Notez que la valeur de type Double du réglage doit être arrondie dans un type entier (Integral).

Nous utilisons une autre échelle horizontale pour gérer la taille de page des trois échelles d’exemple. Lorsqu’elle est réglée à 0.0, les échelles peuvent atteindre leur maximum de 100.0 et quand elle est réglée à 100.0, les échelles sont fixées à leur valeur la plus faible. Cela implique la modification des échelles par un signal onValueChanged provenant d’un troisième Adjustment :

  onValueChanged adj3 $ do val <- adjustmentGetValue adj3
                           adjustmentSetPageSize adj1 val

La fonction principale est:

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

Les fonctions non-standard ayant été énumérées auparavant.