Scrolling sous intuition en C III Programme de Bourse Je vous ai promis une gestion complète du scrolling sans passer par la req.library. Et bien, la voici ! Mais cette fois, il ne s'agit plus de vrai scrolling comme nous l'avons vu précédemment, car au lieu de déplacer une partie de l'écran et de dessiner les parties qui manquent, on va à chaque scrolle tout redessiner. Pour ceux qui se souviennent du précédent article ou je parlais de la req.library et bien c'est comme si on n'utilisait que la routine RedrawAll. Bien entendu cette façon de procéder a d'énormes inconvénients : on perd un temps fou à tout réactualiser l'écran. Alors pourquoi ? Et bien j'ai voulu partager l'explication de "la gestion complète du scrolling sous intuition" (c.a.d en créant sa propre routine RealTimeScroll) en deux parties. Dans la première (celle-ci) nous allons voir comment gérer la structure PropInfo qui est rattachée à tout gadget proportionnel. De plus je vais essayer de vous expliquer les histoires de proportionnalité liées aux gadgets proportionnels. Pour ce faire j'ai trouvé un exemple qui utilise à fond la proportionnalité, et je peux vous dire que si vous arriver à me suivre, le mois prochain il n'y aura plus de problème pour comprendre le reste. Car dans cette deuxième partie on verra comment notre routine RealTimeScroll "décide" entre un RedrawAll et un ScrollAndDraw, sans oublier les paramètres ! Mais revenons à l'exercice du jour. Je ne sais pas si vous suivez la bourse, mais je trouve que s'il y a un truc pénible à suivre c'est... la bourse. Alors pour "suivre" plus facilement la bourse, nous allons créer un petit programme capable de recevoir des "valeurs" boursières et de les afficher graphiquement en fonction du temps. Ca s'appelle un diagramme. Pour compliquer la tâche (et pour utiliser les gadgets proportionnels), le programme devra être capable de "zoomer" sur une partie du graphique. Ainsi vous pourrez voir l'évolution du CAC 40 sur vingt ans ou plus, et vous pourrez zoomer sur une année, puis un mois, puis une semaine. Pour zoomer et dé-zoomer on utilisera deux gadgets booléens (gadgets boutons). Ce programme est en C (voir le titre) car il utilise pas mal de formules mathématiques qui sont déjà assez difficiles à décrypter en C alors en assembleur... (quand je dis formules j'entend par là multiplications et divisions !) De plus, avec le C, on voit tout de suite la structure du programme et on va à l'essentiel (c.a.d, pour nous, comprendre la proportionnalité). Ce qui ne veut pas dire que l'assembleur c'est zéro, au contraire on y reviendra dès le prochain numéro. Encore un mot sur le C : j'utilise la version DP de DICE distribuée par Serge HAMMOUCHE. La principale contrainte réside dans l'absence des nombres à virgule flottante (quoique ce soit plus facile pour ceux qui veulent transcrire le programme en assembleur). Le principal avantage c'est qu'il est DP. Pour ceux qui voudraient utiliser un autre compilateur, ça risque de poser des problèmes pour la gestion de la date : j'utilise les fonctions time() et strftime() ainsi que la structure time_t et je pense qu'elles sont spécifiques à DICE. Ce programme n'utilise pas la req.library (étant donné qu'on n'utilise plus RealTimeScroll) mais la reqtools.library pour les différents requesters (dont le file requester). Passons aux choses sérieuses. Le programme tourne autour d'une liste de petites structures appelées "Quotation". Chacune d'entre-elles contient une date et une valeur. C'est grâce à toutes ces dates et aux valeurs qui leur correspondent qu'on dessine le diagramme d'un titre ou du CAC40 ou plus généralement de tout ce qui évoluent au cour du temps et qui est chiffrable : taille d'un enfant, nombre d'habitants dans un pays, salaire etc. La seule restriction : les valeurs doivent être des nombres entiers (pas de virgule) car la version DP de DICE ne les gère pas. Ainsi pour le cour du dollar vous devez entrer 51130 au lieu de 5,1130 et n'oubliez pas de mettre toujours le même nombre de décimales (ex: 51175 et 51130 et non pas 5113). Les structures Quotations sont reliées entre elles par des pointeurs, on utilise pour cela la sous-structure MinNode. Le programme possède deux options qui permettent de changer soit la date, soit la valeur. La première option appelle la fonction ChangeDate(). Cette dernière demande à l'utilisateur via les requesters d'entrer la nouvelle date. Ensuite elle affiche la nouvelle date en appelant la fonction DisplayDate. La seconde option appelle EnterValue(). Celle-ci demande d'entrer une valeur puis elle cherche parmi les structures quotations s'il n'y en a pas une qui a la même date que la date courante (elle est affichée en bas à droite de l'écran). Si c'est le cas elle recopie la valeur dedans, sinon elle crée une nouvelle structure Quotation, l'initialise et la rattache à la liste. Je passe sous silence les fonctions de chargement et de sauvegarde d'une liste de valeurs. Il n'y a pas grand chose à dire, si ce n'est qu'on utilise directement les fonctions de la dos.library et que les valeurs/dates transitent par une structure spéciale (MinQuot) qui se trouve en CHIP avant de se retrouver sur disquette ou dans la liste. La fonction la plus intéressante elle celle qui s'occupe de l'affichage RedrawDisplay. Pour mieux vous faire comprendre comment elle fonctionne, je vous conseille de vous reporter à l'image IFF ci-jointe (Croquis). Il faut savoir que notre routine est appelée quand l'utilisateur effectue une opération qui risque de modifier le contenu de la liste (chargement, ajout...) et quand il Zoom/UnZoom ou qu'il déplace les ascenseurs. La routine principale (main) qui s'occupe du traitement des messages intuition, ne saute à RedrawDisplay, quand on appuie sur un ascenseur, que si ce dernier à été déplacé. Pour le savoir on met en mémoire leurs positions et on regarde s'il y a eu un changement : if ((oVert != VertSliderInfo.VertPot) || (oHoriz != HorizSliderInfo.HorizPot)) { RedrawDisplay(); oVert = VertSliderInfo.VertPot; oHoriz = HorizSliderInfo.HorizPot; }; oVert et oHoriz contiennent les anciennes positions. VertSliderInfo est une structure PropInfo rattachée à l'ascenseur vertical (VertSlider) et HorizSliderInfo est rattachée à l'ascenseur horizontal. VertPot et HorizPot sont des variables contenues dans chaque structure PropInfo. Elles permettent de connaître la position verticale ou horizontale de l'ascenseur. Voici maintenant la structure PropInfo: WORD Flags peut contenir: AUTOKNOB : indique que c'est intuition qui se charge de dessiner l'ascenseur FREEHORIZ : indique que l'ascenseur peut bouger horizontalement FREEVERT : indique que l'ascenseur peut bouger verticalement WORD HorizPot contient la position horizontale et verticale de WORD VertPot l'ascenseur comprise entre 0 et $FFFF. 0 signifie que l'ascenseur est à gauche ou en bas $FFFF indique que l'ascenseur est à droite ou en haut (voir le texte en vert sur l'image IFF) WORD HorizBody contient la taille de l'ascenseur par rapport à celle WORD VertBody du rectangle qui le contient. La valeur donnée est comprise entre 0 et $FFFF (tout le rectangle). (voir le texte en rouge sur l'image IFF). WORD CWidth tout le reste ne nous intéresse pas. WORD CHeight WORD HPotRes WORD VPotRes WORD LeftBorder WORD TopBorder Attention : le contenu des variables n'a de sens que si les flags correspondant on été positionnés. Ainsi pour l'ascenseur horizontal on positionne le flag FREEHORIZ et on s'occupe du contenu de HorizPot et HorizBody, c'est tout ! VertPot et autre ne nous intéresse pas ! Maintenant, il me reste encore à vous présenter les variables propres au programme. RedrawDisplay cherche dans la liste la plus petite et la plus grande valeur : miny et maxy (y car les valeurs sont représentées par rapport à l'axe des y) ainsi que la plus petite et la plus grande date ( sachant que la liste est triée chronologiquement, elle se contente de prendre la première date et la dernière) : minx et maxx. Elle obtient ainsi l'écart maximal des valeurs et des dates qui correspond à la taille totale du diagramme : ampx et ampy ('amp' signifie amplitude). RedrawDisplay doit encore déterminer la position de l'écran dans ce diagramme. Cette position est justement contenue dans HorizPot et VertPot (NDLA : comme on se retrouve !) et la taille de l'écran est dans HorizBody et VertBody. Il ne reste plus qu'à transformer ces valeurs pour obtenir basex, basey, viewsizex et viewsizey. basex et basey contiendront les plus petites valeurs/dates qui peuvent être affichées. viewsizex+basex et viewsizey+basey contiendront les plus grandes valeurs/dates. Pour obtenir ces valeurs il faut savoir que : HorizBody/MAXBODY = viewsizex/ampx HorizPot/MAXPOT = basex/ampx-viewsizex de même pour "Vert" MAXBODY et MAXPOT sont des constantes contenant $FFFF. Ainsi si HorizBody vaut $FFFF, alors viewsizex = ampx, ce qui est vrai et qui signifie que l'écran contient tout le diagramme. Si HorizPot vaut 0, alors basex = 0, ce qui est vrai car l'écran se trouve au début du diagramme. Si HorizPot vaut $FFFF, alors basex = ampx-viewsizex, ce qui est vrai car dans ce cas l'écran est en bout de diagramme, et basex est égal à la taille maximale du diagramme (ampx) moins celle de l'écran (viewsizex). Ca donne en C: viewsizex = (ULONG) ampx * HorizSliderInfo.HorizBody / MAXBODY; viewsizey = (ULONG) ampy * VertSliderInfo.VertBody / MAXBODY; basex = (ULONG) minx + ((ULONG) ((ampx - viewsizex) * HorizSliderInfo.HorizPot) / MAXPOT); basey = ((ULONG) (ampy - viewsizey) * (MAXPOT - VertSliderInfo.VertPot) / MAXPOT) + miny; On ajoute minx et miny, sinon basex et basey correspondraient au début de l'écran par rapport au début du diagramme et non par rapport à zéro. C'est grâce à ces quatre variables que l'affichage va pouvoir se faire dans les meilleurs conditions et nous allons l'aborder sans plus tarder. Pour faciliter la compréhension je vous conseille d'avoir sous les yeux l'image IFF Croquis2. RedrawDisplay cherche d'abord le premier point à afficher, à savoir le premier qui est supérieur à basey. Si c'est le premier point de la liste, pas de problème on peut passer directement à son traitement dans la routine MinRD (voir ci-dessous). Si ce n'est pas le cas, alors il faut d'abord trouver la position du pseudo-point (le premier point rouge sur l'image IFF) compris entre le premier point affiché et le point précédent (le point entouré en jaune dans l'image IFF), car il faut bien débuter au début de l'écran. Ensuite on appelle la fonction MinRD jusqu'à ce qu'on atteigne la fin de la liste ou le bord de l'écran. Cette fonction teste d'abord la valeur du flag nommé flag et qui permet de savoir si c'est le premier point d'une droite ou s'il y en a déjà un (NDLA : de point) (si c'est le premier on fera un Move, sinon ce sera un Draw). Dans le cas d'un premier point (points rouges sur l'image IFF), il faut déterminer sa position par rapport à l'écran : Il peut être dedans auquel cas, il n'y a pas grand chose à faire si ce n'est l'initialiser avec Move (je dis initialiser car Move ne dessine rien à l'écran mais il fixe les coordonnées pour le Draw qui suit). Il peut être au-dessus, et là, deux autres cas : soit le point qui suit est aussi au-dessus et on affichera donc rien, soit il est dedans ou au-dessous et dans ce cas, il y aura une droite. Dans ce dernier cas, on doit calculer un pseudo-point (un rouge). Dernier cas, le point est en-dessous de l'écran et là aussi, il faut regarder ou se situe le point qui suit. Passons maintenant aux cas ou il ne s'agit pas du premier point. Le point peut être trop bas : dans ce cas il faut calculer le pseudo-point final (en bleu sur l'image IFF) et mettre le flag FALSE, pour que le prochain point soit traite comme un "premier point", car on ne pourra pas faire directement une ligne entre lui et notre point. Le point est trop haut : idem que précédemment. Le point est dans l'écran : alors on continue à Draw()er allègrement en laissant flag TRUE. Tout à l'heure je vous disais que vous alliez nager dans la proportionnalité. Et bien oui ! Alors attaquons le croquis3. Je ne redirai pas ce qu'il y a déjà écris dedans, mais si vous suivez bien vous devriez trouvez un truc bizarre : le début de l'écran correspond à (0,0), alors que d'après ce que j'ai dis précédemment il devrait être à (basex,basey). Mais pour simplifier les calculs et pour éviter d'avoir de trop grands nombres, j'ai fais en quelque sorte un changement de repère en soustrayant toutes les coordonnées par basex ou basey (suivant qu'on parle de la date ou de la valeur). Ainsi on peut comparer directement avec viewsizex, au lieu de passer par basex+viewsizex. Dernière chose : l'exemple du croquis3 montre le cas ou on doit calculer le premier point à afficher, mais pour tous les cas ou il y a un débordement vertical, on n'a qu'à tourner le triangle de 90 degrés : le principe reste le même et vous n'avez plus qu'à vérifier les formules :-) Dernière chose à propos du programme : j'ai passé sous silence les variables coefx et coefy alors qu'elles tiennent un rôle primordial : à vous de le trouver et de m'envoyer les réponses. Comme vous avez du vous en rendre compte, on s'est beaucoup éloigner du scrolling. Mais en réalité c'est "reculer pour mieux sauter". Si vous jonglez parfaitement avec ses principes alors il n'y aura plus de problème. Pour anecdote sachez qu'il y a trois niveaux de proportionnalité imbriquées dans le programme : intuition et ses gadgets, la gestion de points dans le diagramme imaginaire et la restitution sur l'écran (celle-là je n'en ai pas parlé mais elle est juste avant Move et Draw). N'oubliez pas de charger les deux mini-diagrammes qui représentent l'évolution du CAC40 et celle du dollar pendant trois semaines de septembre. En parlant de semaine, le programme à une grosse lacune qui ne demande qu'à être comblée : l'affichage des dates et des valeurs des points au-dessus et à droite des ascenseurs ! A vos clavier ! (NDLA : comme dirait l'autre) Le concours "le premier qui m'envoie son programme amélioré, aura droit à... toute mon estime" tient toujours... Christophe LARATTE 6 allée Marcel Pagnol 87410 Le Palais sur Vienne