Édition des objets d'un canevas

Édition des objets d'un canevas

Ph.Leroy

2019-01-07

Définition des objets d'un canevas

Un canevas permet de présenter facilement des objets actifs, il se compose d'une déclaration de canevas comprenant une liste de composant.

Include "widget.sch";
Object
cv0 Canvas, "", 10, 10, 400, 300, begin
   bt1 button, "Premier", 10, 10, 120, 20;
   bt2 button, "Second", 10, 10, 120, 20;      
end;

On obtient une vue comme celle-ci.

Il s'agit bien d'un canevas puisque on dessine son fond d'écran dans une call-back. Par contre les boutons sont standards ils sont juste placés dans le canevas.

Synoptic Test( )
Var w; h;
begin
   on do begin 
      cv0.SetPaintFunction( cnPaint);
   end
   
   on evsize do begin
      getwindowsize( w, h);
      cv0.Place( 0, 0, w, h);
   end
   
end

Le code est simple, on définit la fonction de dessin dans la clause d'initialisation. On profite de l'événement evSize pour replacer le canevas bien centré dans la surface de la fenêtre.

Définition de widgets

Un widget est un objet sisal associé à un élément de dialogue homme-machine.

Chaque widget dérive de la classe widget qui définit sa position, sa taille et les autres attributs standards comme un texte associé, une visibilité et même un style.

En général on définit un constructeur (create) et un peintre (paint) dans la déclaration de la classe.

Prenons l'exemple de la création d'un nœud d'un graphe.

mynode class widget begin
   color; // Couleur d'affichage
   create( text, x, y, w, h, color); 
   paint( cdc dc);
   print(); 
end;

Donc un mynode est un widget; il définit le create qui a les paramètres standards des déclarations d'objets de synoptiques et ses paramètres propres comme sa couleur.

Object
cv0 Canvas, "", 10, 10, 400, 300, begin
   bt1 button, "Premier", 10, 10, 120, 20;
   mn2 mynode, "Second", 10, 10, 120, 120, rgb(192,192,255);      
end;

Les méthodes associées sont par exemple:

Function mynode.create( libel_, x_, y_, w_, h_, color_)
// Les parametres sont renommes pour ne pas masquer les attributs de widget
begin
   widget.create( libel_, x_, y_, w_, h_); // On appelle directement la méthode de la classe mere
   color = color_;
   style = NO_BORDER | SS_LEFT;
end

Function mynode.paint( cdc dc)
var wa; ha;
begin
   cdc.setpen( RGB(0,0,0), 1, 1);
   cdc.setbrush( color);
   cdc.drawRectangle( 0, 0, w, h); // Un rectangle beu clair
   
   cdc.setFont( LaFonte, LaTaille);
   cdc.GetTextExtent( text, wa, ha);
   
   cdc.DrawText( text, (w-wa)/2.0, 2); // Le nom de l'objet affiche en haut au milieu
   cdc.DrawLine( 0, ha, w, ha); 
   
end

Function mynode.print()
// donne le texte correspondant a la declaration de l'objet
// Utilise en autre par le debogueur et l'editeur des objets d'IHM
Var str; rouge; vert; bleu;
begin
   color2RGBA( color, rouge, vert, bleu);
   return """:text:"", ":x:", ":y:", ":w:", ":h:", RGB( ":rouge: ", ":vert:", ":bleu:")";
end

Édition des objets d'IHM

Lorsque on définit une interface homme-machine plus complexe, par exemple un synoptique avec des réservoirs et des tuyaux, on peut se trouver dans une situations où cela devient difficile d'aligner ses widgets.

Sisal permet de déplacer ces widgets dans la vue du synoptique et de pouvoir sauvegarder ces coordonnées mises à jour.

Pour activer cette édition des objets, c'est très simple il suffit, après s'être connecté en tant qu'administrateur, d'appuyer sur la touche "Control" du clavier quand on fait une action à la souris.

Action Actions
Ctrl + Clic gauche Sélection ou désélection d'un objet, le widget sélectionné est encadré d'un cadre coloré, sa déclaration est affichée
Glisser-déplacer En cliquant au centre du cadre de sélection puis en glissant, on peut déplacer le widget sélectionné
+ En faisant la même chose prés d'un des coins on peut redimensionner le widget
Ctr + Clic droit Apparition d'un menu contextuel permettant de copier, couper ou coller le widget sélectionné
+ Enregistrer Enregistrement dans un fichier texte ( monsynoptique.synobj ) de toutes les déclarations du synoptique mise à jour; dans un monde idéal il faudrait que l'on mette à jour directement le fichier source (monsynoptique.syn), mais cela est difficile à faire en raison des commentaires qui peuvent être n'importe où.

Par exemple, on a deux widgets

On sélectionne celui de gauche

On déplace celui de gauche

Et on sauvegarde les déclarations d'objets

Dans le fichier généré, on a les déclarations avec les positions mises à jour.

tc0 TabControl, "", 0, 0, 386, 246, , , "Swiss", 12, begin    
   tsExplor TabSheet[ 1, 1, 10, 10, 10, 32], "View", 0, 0, 386, 246, begin    
   
      cn0 Canvas, "", 5, 5, 366, 204, NO_BORDER, begin     
         rd1 mynode, "Node1", 20, 20, 48, 32, RGB( 255, 255, 0);      
         rd2 mynode, "Node2", 220, 120, 48, 32, RGB( 255, 155, 0);      
         hw1 mylink, "", 67, 34, 153, 102, {{ 0, 0}, { 66, 0}, { 66, 100}, { 155, 100}};
      end;
   end;   
   tsLog TabSheet[ 1, 1, 10, 10, 10, 32], "Log", 0, 0, 386, 246, begin    
      lbLog ListBox, "", 5, 5, 366, 204, SB_VERTICAL | BS_MULTILINE;
   end;
end;

Bon il y a quelques erreurs comme la valorisation des attributs déduits des tableaux d'alignement ( TabSheet[ 1, 1, 10, 10, 10, 32]), mais on a nos coordonnées mises à jour et on peut faire une insertion dans notre fichier source, suivi d'une réindentation.

Avec "Enregistrer" ce sont toutes les déclarations qui sont transmises, y compris celles qui sont définies hors du canevas.

Widgets à poignées

Souvent on est amené à représenter des liens matérialisant une relation entre deux entités, par exemple un tuyau dans une raffinerie; cela ne peut être un widget simple car à chaque déplacement d'une extrémité on serait obligé de mettre à jour le code de la méthode de peinture du widget (méthode paint).

On a déjà vu que l'on pouvait "attraper" un widget dessiné dans un canevas à cinq endroits particuliers, au centre et à chacun de ses coins; ce endroits sont des poignées.

Pour un lien, par exemple, on va pouvoir définir d'autres points particuliers comme les points d'inflexion du lien. Ces points vont pouvoir être sélectionnés et déplacés; ce sont des poignées que l'on va identifier dans une liste de points qui va être échangées entre le canevas dessiné et les méthodes des objets dessinés dans le canevas.

Un widget à poignée est un widget qui offre deux méthodes supplémentaires, qui permettent au synoptique de connaître toutes les poignées du widget (méthode gethandles()) et de mettre à jour la liste de ces poignées (méthode sethandles). Il est alors possible au synoptique en cours d'édition de détecter un clique à proximité d'une de ces poignées, de la sélectionner et de pouvoir la faire glisser pour la placer au bon endroit.

mylink class widget begin
   handles list; // Liste des poignees d'edition

   create( libel, x, y, w, h, hnd list); // On attend une liste de point
   paint( mydc dc);
   print(); // Capable de retourner la declaration d'un objet
   gethandles();
   sethandles( hnd list);
   messagex();
end;

On introduit une nouvelle classe de widget "mylink"; elle dispose d'un attribut supplémentaire "handles" qui est une liste de points ( un point est ici une liste de deux entiers x et y) et les deux méthodes "gethandles" et "sethandles".

On va pouvoir déclarer ce widget à poignée.

     hw1 mylink, "", 67, 34, 153, 102, {{  0,  0}, {  66,  0}, {  66,  100}, {  155,  100}};

On repère nos paramètres habituels (x, y, w, et h) et le nouveau paramètre qui est une liste de 4 points ( 0x0, 66x0, 66x100 et 100x66). On remarque aussi que les coordonnées x et y sont absolues ( c'est à dire relative au canevas) et celles des poignées sont relatives au widget lui-même (si on déplace le widget, les coordonnées des poignées ne sont pas modifiées).

En sélectionnant hw1 on obtient cette vue.

Enfin en cliquant à proximité d'une poignée on va pouvoir déplacer cette poignée.

La sauvegarde et la copie conserveront ce nouvel emplacement de poignée.

Vu du coté du code, cela est très simple; lorsque l'utilisateur sélectionne un widget, le synoptique appelle la méthode "gethandles", il obtient la liste des poignées et connaît déjà les cinq poignées de tout widget.

Quand l'utilisateur clique dans le widget sélectionné, le synoptique identifie la poignée la plus proche et la sélectionne, tout glissement induit entraînera le déplacement effectif de cette poignée; lorsque utilisateur relâche le bouton, le synoptique transmet à l'objet sélectionné la liste mise à jour des poignées en appelant "sethandles" et redessine tous les widgets. Notre widget affiche alors sa poignée déplacée.

function mylink.create( libel_, x_, y_, w_, h_, hnd list)
begin
   widget.create( libel_, x_, y_, w_, h_);
   handles = hnd;
end

function mylink.paint( cdc dc)
var iter; prime list; seconde list;
begin  
   cdc.setpen( RGB( 64, 64, 255), 2, 3);
   cdc.setbrush( RGB( 64, 255, 64), 0);
   for iter in handles do begin
      if prime.size() == 0 then prime = value( iter);
      else begin
         seconde = value( iter);
         cdc.DrawLine( int( prime[0]), int(prime[1]), int(seconde[0]), int(seconde[1]));
         prime = seconde;
      end
   end
end

function mylink.print()
// Retourne la déclaration utilisée par le debogueur avec la liste des poignées
// "", 10, 10, 100, 90, { { 0, 0}, { 20, 0}, { 20, 20}, { 40, 20}, {40, 40}}
Var str; initie; pt;
begin
   str = """, ":x:", ":y:", ":w:", ":h:", {";
   initie = null;
   for pt in handles do begin
      if initie == null then initie = 1;
      else str = str:", ";
      str = str:value(pt);
   end
   str = str:"}";
   return str;
end

function mylink.gethandles()
begin
   return handles;
end

function mylink.sethandles( hnd list)
begin
   handles = hnd;
end

Il est à noter que l'on peut rajouter ou supprimer des poignées.

Et le code exemple n'a même pas besoin d'être modifié.