Les widgets de type DrawingArea


Table des matières :


Introduction :


Le widget de type DrawingArea permet de dessiner à l'aide
des fonctions graphique de la Xlib que nous avons étudiées au début de l'année.

Création d'une DrawingArea :

Pour créer une DrawingArea on utilisera au choix XmCreateDrawingArea() ou XtVaCreateManagedWidget() avec comme classe XmDrawingAreaWidgetClass.

Pour compiler sans erreurs il faut inclure le fichier <Xm/DrawingA.h>.

Il existe aussi un widget de type DrawnButton qui est la combinaison d'une DrawingArea et d'un PushButton. Si on utilise des DrawnButton dans une application, il faut inclure <Xm/DrawnB.h>.


Ressources et callbacks des widget de type DrawingArea


En général on place les DrawingArea à l'intérieur d'un container widget : une Form, une MainWindow, une ScrolledWindow, etc... si bien qu'il est rare que l'on doive spécifier sa taille car cette dernière est héritée du widget père. On peut cependant positionner les ressources XmNheight et XmNwidth pour fixer la taille.

La ressource XmNresizePolicy permet de règler les autorisations de changement de taille par le window manager. Trois valeurs possibles : XmRESIZE_ANY, XmRESIZE_GROW et XmRESIZE_NONE.

Par défaut on peut associer trois types de callbacks à une DrawingArea :

XmNexposeCallback
Ce callback est appelé lorsqu'une partie de la DrawingArea doit être redessinnée.

XmNresizeCallback
Si la taille de la DrawingArea change ce callback est appelé.

XmNinputCallback
Si une entrée clavier ou un click souris est effectué dans la DrawingArea, ce callback est appelé.


Utilisation pratique d'une DrawingArea


Nous allons maintenant réaliser du dessin 2D dans une application MOTIF. Toutes les opérations graphiques seront réalisées à l'aide de fonctions de la Xlib, il va falloir récurpèrer des informations de bas niveau à partir des widgets MOTIF possèdant un de haut niveau d'abstraction.

Nous allons étudier de petits programmes d'exemples. Tous procèdent de la même manière :

  1. Création des différents widgets de l'application, y compris bien sûr une DrawingArea
  2. Obtention d'informations nécessaires aux fonctions Xlib : Display, pointeurs sur l'ID de la fenêtre X de la DrawingArea, etc...
  3. Dessin dans la fenêtre X.

Premier programme d'exemple: draw.c

Ce petit programme illustre les principes de base de l'utilisation conjointe de fonctions Xlib, Xt et MOTIF.

L'exécutable de draw.c se trouve dans :
    /home/profs/cours/minfo/motif/drawing/draw
Voici ce que produit le programme draw.c :



#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/DrawingA.h>
#include <Xm/RowColumn.h>
/* XLIB Data */

Display *display;
Screen *screen_ptr;

/*--------------------------*/
main(int argc, char *argv[])
/*--------------------------*/
{   
  Widget top_wid, main_w, menu_bar, draw, quit;
  XtAppContext app;
  XGCValues gcv;
  GC gc;
  void quit_call(), draw_cbk();

  top_wid = XtVaAppInitialize(&app, "Draw", NULL, 0, 
			      &argc, argv, NULL,
			      XmNwidth,  500,
			      XmNheight, 500,
			      NULL);

  /* create a main window */
  main_w = XtVaCreateManagedWidget("main_window",
				   xmMainWindowWidgetClass, top_wid,
				   NULL);

  /* create a menu bar */
  menu_bar = XmCreateMenuBar(main_w, "main_list", 
			     NULL, 0);        
  XtManageChild(menu_bar);
      
  /* create quit widget in menu bar + callback */
  quit = XtVaCreateManagedWidget( "Quit",
				  xmCascadeButtonWidgetClass, menu_bar,
				  XmNmnemonic, 'Q',
				  NULL);

  XtAddCallback(quit, XmNactivateCallback, quit_call, 
		NULL);

  /* Create a DrawingArea widget. */
  draw = XtVaCreateWidget("draw",
			  xmDrawingAreaWidgetClass, main_w,
			  NULL);
        
  /* get XLib Display Screen and Window ID's for draw */
  display = XtDisplay(draw);
  screen_ptr = XtScreen(draw);

       
  /* set the MenuBar and the DrawingArea as menu bar and the 
     "work area" of main window. Like that they are partly managed
     by the MainWindow */
  XtVaSetValues(main_w,
		XmNmenuBar,    menu_bar,
		XmNworkWindow, draw,
		NULL);
        
  /* add callback for exposure event */
  XtAddCallback(draw, XmNexposeCallback, draw_cbk, NULL);


  /* Create a GC. Attach GC to the DrawingArea's 
     XmNuserData.
     NOTE : This is a useful method to pass data */
  gcv.foreground = BlackPixelOfScreen(screen_ptr);
  gc = XCreateGC(display,
		 RootWindowOfScreen(screen_ptr), GCForeground, &gcv);
  XtVaSetValues(draw, XmNuserData, gc, NULL);

  /* because we changed some ressources after the Managed Creation */
  XtManageChild(draw);

  XtRealizeWidget(top_wid);
  XtAppMainLoop(app);
}

/* CALL BACKS */

/*--------------*/
void quit_call()
/*--------------*/
{   
  printf("Quitting program\n");
  exit(0);
}

/*----------------------------------------------*/
void draw_cbk(Widget w, XtPointer data,
              XmDrawingAreaCallbackStruct *cbk)
/*----------------------------------------------*/
/*  DrawingArea Callback. NOTE: cbk->reason says type of 
    callback event */
{   
  char  str1[25];
  int len1, width1, font_height;
  unsigned int width, height;
  int  x, y, angle1, angle2, x_end, y_end;
  unsigned int line_width = 1;
  int line_style = LineSolid;
  int cap_style = CapRound;
  int join_style = JoinRound;    
  XFontStruct *font_info;
  XEvent *event = cbk->event;
  void load_font();
  GC gc;
  Window win = XtWindow(w);


  if (cbk->reason != XmCR_EXPOSE) { 
    /* Should NEVER HAPPEN for this program */
    printf("X is screwed up!!\n");
    exit(0);
  } 
     
  /* get font info */
  load_font(&font_info);
  font_height = font_info->ascent + font_info->descent;

       
  /* get gc from Drawing Area user data */
  XtVaGetValues(w, XmNuserData, &gc, NULL);

  /* Draw A Rectangle */
  x = y = 10;
  width = 100;
  height = 50;
 
  XDrawRectangle(display, win, gc, x, y, width, height);

  strcpy(str1,"RECTANGLE"); 
  len1 = strlen(str1);
  y += height + font_height + 1; 

  if ((x = (x + width/2) - len1/2) < 0 ) 
    x = 10;
  
  XDrawString(display, win, gc, x, y, str1, len1);

  /* Draw a filled rectangle */
  x = 10; y = 150;
  width = 80;
  height = 70;

  XFillRectangle(display, win, gc, x, y, width, height);

  strcpy(str1,"FILLED RECTANGLE"); 
  len1 = strlen(str1);
  y += height + font_height + 1; 
  if ((x = (x + width/2) - len1/2) < 0 ) 
    x = 10;
  
  XDrawString(display, win, gc, x, y, str1, len1);

  
  /* draw an arc */
  x = 200; y = 10;
  width = 80;
  height = 70;
  angle1 = 180 * 64; /* 180 degrees */
  angle2 = 90 * 64;  /* 90 degrees */

  XDrawArc(display, win, gc, x, y, width, height, 
           angle1, angle2);


  strcpy(str1,"ARC"); 
  len1 = strlen(str1);
  y += height + font_height + 1; 
  if ((x = (x + width/2) - len1/2) < 0 ) 
    x = 200;
  
  XDrawString(display, win, gc, x, y, str1, len1);


  /* draw a filled arc */
  x = 200; y = 200;
  width = 100;
  height = 50;
  angle1 = 270 * 64; /* 270 degrees */
  angle2 = 180 * 64; /* 180 degrees */

  XFillArc(display, win, gc, x, y, width, height, 
           angle1, angle2);

  strcpy(str1,"FILLED ARC"); 
  len1 = strlen(str1);
  y += height + font_height + 1; 
  if ((x = (x + width/2) - len1/2) < 0 ) 
    x = 200;
  
  XDrawString(display, win, gc, x, y, str1, len1);


  /* SOLID LINE */
  x = 10; y = 300;
  /* start and end points of line */
  x_end = 200; y_end = y - 30; 
  XDrawLine(display, win, gc, x, y, x_end, y_end);

  strcpy(str1,"SOLID LINE"); 
  len1 = strlen(str1);
  y += font_height + 1; 
  if ((x = (x + x_end)/2 - len1/2) < 0 ) 
    x = 10;

  XDrawString(display, win, gc, x, y, str1, len1);


  /* DASHED LINE */
  line_style = LineOnOffDash;
  line_width = 2;

  /* set line attributes */
  XSetLineAttributes(display, gc, line_width, line_style, 
		     cap_style, join_style);

  x = 10; y = 350;
  /* start and end points of line */
  x_end = 200; y_end = y - 30; 
  XDrawLine(display, win, gc, x, y, x_end, y_end);

  strcpy(str1,"DASHED LINE"); 
  len1 = strlen(str1);
  y += font_height + 1; 
  if ((x = (x + x_end)/2 - len1/2) < 0 ) x = 10;

  XDrawString(display, win, gc, x, y, str1, len1);
}

/*--------------------------------------*/
void load_font(XFontStruct **font_info)
/*--------------------------------------*/
{  
  char *fontname = "fixed";
  XFontStruct *XLoadQueryFont();

  /* load and get font info structure  */

  if (( *font_info = XLoadQueryFont(display, fontname)) == NULL) { 
    /* error - quit early */
    printf("%s: Cannot load  %s font\n", "draw.c", 
	   fontname); 
    exit(-1);
  }
}


Deuxième exemple : le programme draw_input.c

Avec cet exemple, nous allons voir comment traiter les entrées/sorties dans une DrawingArea.

Nous reprenons le programme menu_pull.c déjà étudié lors du cours sur les MainWindow et les menus, et le modifions pour intégrer une DrawingArea. Ce programme permet le dessin de rectangles "à la Mac Draw", le menu "colors" servant à sélectionner la couleur du rectangle que l'on veut dessiner.

Dump d'écran de draw_input.c :

L'exécutable se trouve dans :

    /home/profs/cours/minfo/motif/drawing/draw_input
Nous avons déjà vu pendant les TDs Xlib comment il fallait procèder pour dessiner des rectangles "élastiques". Il faut traiter trois types d'événements : l'appui d'un bouton de la souris, le déplacement de la souris et le relâchage du bouton souris.

Les DrawingArea widgets possèdent deux ressources permettant d'appeler des callbacks en cas d'entrées clavier ou souris, mais ils ne possèdent pas de ressources permettant de traiter le déplacement de la souris.

La solution consiste à enrichir la table de translations de la DrawingArea.

Rappel: chaque widget possède une table de translations qui se compose de :

Extrait d'une table de translation (celle de netscape, prise dans son fichier de ressources /usr1/local/lib/X11/app-defaults/Netscape) :



    .
    .
    .
    <Btn1Down>:      ArmLink() 
    <Btn2Down>:      ArmLink() 
    .
    .
    .


Pour ajouter le traitement de l'événement "déplacement de la souris bouton 1 appuyé", il faut ajouter la translations suivante :



    <Btn1Motion>: draw_cbk(motion)


draw_cbk() est la fonction de callback qui effectue le dessin des rectangles. Chaque fois que la souris sera déplacée bouton 1 appuyé dans la DrawingArea, draw_cbk() sera appelée. De plus, il est possible de passer à la fonction de callback des paramètres de type String! Avec l'exemple ci-dessus, la chaîne de caractères "motion" sera passée (plus de détails sur le traitement de ces paramètres seront donnés page suivante).

Bien que l'on puisse traiter les autres événements souris avec les méthodes d'ajout de callback standards (XtAddCallBack(), etc...), nous allons nous servir de la table de translation pour traiter l'appui et le relâchement du bouton 1 de la souris. La table de translation finale ressemble à ceci :



    <Btn1Motion>: draw_cbk(motion)
    <Btn1Up>: draw_cbk(up)
    <Btn1Down>: draw_cbk(down)


En pratique, dans un programme MOTIF, nous initialisons d'abord la table de translations dans une variable de type String, puis l'on attache cette table à la DrawingArea en positionnant la ressource XmNtranslations avec comme valeur le résultat de
XtParseTranslationTable(String translations).

Il faut également, lors de la création de la DrawingArea, indiquer que nous avons enrichi la table de translations par défaut avec les actions associées aux nouvelles translations. Ceci est réalisé à l'aide de la fonction XtAppAddActions(). Ainsi il sera possible de postionner ces nouvelles translations dans un fichier de ressources, l'application reconnaîtra les nouveaux événements.

Avec notre exemple :

XtActionsRec actions; ... actions.string = "draw_cbk"; actions.proc = draw_cbk; XtAppAddActions(app, &actions, 1);

Récupération des paramètres par la fonction de callback:

Voici l'en-tête de la fonction de callback:



void  draw_cbk(Widget w, XButtonEvent *event, 
               String *args, int *num_args)
{
   .
   .
   .
}


Les paramètres sont passés dans la variable args, le nombre de paramètres dans num_args. Comme nous ne passons qu'un seul paramètre avec la table de translations correspondant à cet exemple, nous testerons juste la valeur de la variable args[0]. es différentes valeurs up, motion and down nous renseigneront sur la nature de l'événement qui a déclenché l'appel de la fonction de callback.

listing complet du programme draw_input.c :



#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/DrawingA.h>
#include <Xm/RowColumn.h>

GC gc;
XGCValues gcv;
Widget draw;
String colours[] = { "Black",  "Red", "Green", "Blue", 
                     "Grey", "White"};
long int fill_pixel = 1; /* stores current colour 
                         of fill - black default */
Display *display; /* xlib id of display */
Colormap cmap;

/*---------------------------*/
main(int argc, char *argv[])
/*---------------------------*/
{   
  Widget top_wid, main_w, menu_bar, quit, clear, colour;
  XtAppContext app;
  XmString  quits, clears, colourss, red, green, 
            blue, black, grey, white;
  void quit_call(), clear_call(), colour_call(), 
       draw_cbk();

  XtActionsRec actions;
  String translations = 
    "<Btn1Motion>: draw_cbk(motion)  \n\
     <Btn1Down>: draw_cbk(down)  \n\
     <Btn1Up>: draw_cbk(up) ";

  top_wid = XtVaAppInitialize(&app, "Draw", NULL, 0, 
			      &argc, argv, NULL,
			      XmNwidth,  500,
			      XmNheight, 500,
			      NULL);

  /* Create MainWindow */
  main_w = XtVaCreateManagedWidget("main_window",
				   xmMainWindowWidgetClass,   top_wid,
				   XmNwidth, 500,         
				   XmNheight, 500,
				   NULL);
        
  /* Create a simple MenuBar that contains three menus */
  quits = XmStringCreateSimple("Quit");
  clears = XmStringCreateSimple("Clear");
  colourss = XmStringCreateSimple("Colour");

        
  menu_bar = XmVaCreateSimpleMenuBar(main_w, "main_list",
				     XmVaCASCADEBUTTON, quits, 'Q',
				     XmVaCASCADEBUTTON, clears, 'C',
				     XmVaCASCADEBUTTON, colourss, 'o',
				     NULL); 
  XtManageChild(menu_bar);


  /* First menu is quit menu -- callback is quit_call() */
  XmVaCreateSimplePulldownMenu(menu_bar, "quit_menu", 0, 
	  quit_call, XmVaPUSHBUTTON, quits, 'Q', NULL, NULL,
	  NULL);
  XmStringFree(quits);
    
  /* Second menu is clear menu -- callback is clear_call() */
    
  XmVaCreateSimplePulldownMenu(menu_bar, "clear_menu", 1, 
	  clear_call, XmVaPUSHBUTTON, clears, 'C', NULL, NULL,
	  NULL);
  XmStringFree(clears);

    
  /* create colour pull down menu */
    
  black = XmStringCreateSimple(colours[0]);
  red = XmStringCreateSimple(colours[1]);
  green = XmStringCreateSimple(colours[2]);
  blue = XmStringCreateSimple(colours[3]);
  grey = XmStringCreateSimple(colours[4]);
  white = XmStringCreateSimple(colours[5]);

    
  colour = XmVaCreateSimplePulldownMenu(menu_bar, 
	       "edit_menu", 2, colour_call,
	       XmVaRADIOBUTTON, black, 'k', NULL, NULL,
	       XmVaRADIOBUTTON, red, 'R', NULL, NULL,
	       XmVaRADIOBUTTON, green, 'G', NULL, NULL,
	       XmVaRADIOBUTTON, blue, 'B', NULL, NULL,
	       XmVaRADIOBUTTON, grey, 'e', NULL, NULL,
	       XmVaRADIOBUTTON, white, 'W', NULL, NULL,
	       XmNradioBehavior, True,     
	       /* RowColumn resources to enforce */
	       XmNradioAlwaysOne, True,    
	       /* radio behavior in Menu */
	       NULL);

  XmStringFree(black);
  XmStringFree(red);
  XmStringFree(green);
  XmStringFree(blue);
  XmStringFree(grey);
  XmStringFree(white);


  /* Create a DrawingArea widget. */
  /* make new actions */
  actions.string = "draw_cbk";
  actions.proc = draw_cbk;
  XtAppAddActions(app, &actions, 1);
    
  draw = XtVaCreateWidget("draw",
	     xmDrawingAreaWidgetClass, main_w,
	     XmNtranslations, XtParseTranslationTable(translations),
	     XmNbackground, WhitePixelOfScreen(XtScreen(main_w)),
	     NULL);

        
  cmap = DefaultColormapOfScreen(XtScreen(draw));
  display = XtDisplay(draw);
    
/* set the DrawingArea as the "work area" of main window */
  XtVaSetValues(main_w,
		XmNmenuBar,    menu_bar,
		XmNworkWindow, draw,
		NULL);


/* Create a GC. Attach GC to DrawingArea's XmNuserData. */
  gcv.foreground = BlackPixelOfScreen(XtScreen(draw));
  gc = XCreateGC(XtDisplay(draw),
		 RootWindowOfScreen(XtScreen(draw)), 
		 GCForeground, &gcv);

  XtManageChild(draw);
  XtRealizeWidget(top_wid);
  XtAppMainLoop(app);
}


/* CALL BACKS */

/*--------------*/
void quit_call()
/*--------------*/
{  
  printf("Quitting program\n");
  exit(0);
}

/*---------------*/
void clear_call() 
/*---------------*/
/* clear work area */
{ 
  XClearWindow(display, XtWindow(draw)); 
}

/*---------------------------*/
void colour_call(w, item_no)
/*---------------------------*/
/* called from any of the "Colour" menu items.  
   Change the color of the
   label widget. 
   Note: we have to use dynamic setting with setargs()..
 */
Widget w;     /* menu item that was selected */
int item_no;  /* the index into the menu */
{
  int n =0;
  Arg args[1];
    
  XColor xcolour, spare; /* xlib color struct */

    
  if (XAllocNamedColor(display, cmap, colours[item_no], 
		       &xcolour, &spare) == 0)
    return;
    
  /* remember new colour */        
  fill_pixel = xcolour.pixel;     
}

/*  DrawingArea Callback.*/ 

/*--------------------------------------------*/ 
void  draw_cbk(Widget w, XButtonEvent *event, 
               String *args, int *num_args)
/*--------------------------------------------*/ 
{   
  static Position x, y, last_x, last_y;
  Position width, height;
    
  int line_style;
  unsigned int line_width = 1;
  int cap_style = CapRound;
  int join_style = JoinRound;
   
  if (strcmp(args[0], "down") == 0) {  
    /* anchor initial point (save its value) */
    x = event->x;
    y = event->y;
  } 
  else 
    if (strcmp(args[0], "motion") == 0) { 
      /* draw "ghost" box to show where it could go */
      /* undraw last box */
             
      line_style = LineOnOffDash;
      /* set line attributes */

      XSetLineAttributes(event->display, gc,  
	    line_width, line_style, cap_style, join_style);
             
      gcv.foreground 
	= WhitePixelOfScreen(XtScreen(w));

      XSetForeground(event->display, gc, 
		     gcv.foreground);
             
      XSetFunction(event->display, gc, GXinvert);

             
      XDrawLine(event->display, event->window, gc,  
		x, y, last_x, y);
      XDrawLine(event->display, event->window, gc, 
		last_x, y, last_x, last_y);
      XDrawLine(event->display, event->window, gc, 
		last_x, last_y, x, last_y);
      XDrawLine(event->display, event->window, gc,  
		x, last_y, x, y);

         
      /* Draw New Box */
      gcv.foreground 
	= BlackPixelOfScreen(XtScreen(w));
      XSetForeground(event->display, gc, 
		     gcv.foreground);
          
      XDrawLine(event->display, event->window, gc, 
		x, y, event->x, y);
      XDrawLine(event->display, event->window, gc, 
		event->x, y, event->x, event->y);
      XDrawLine(event->display, event->window, gc, 
		event->x, event->y, x, event->y);
      XDrawLine(event->display, event->window, gc,  
		x, event->y, x, y);
    }
    else       
      if (strcmp(args[0], "up") == 0) {
	/* draw full line */
          
	XSetFunction(event->display, gc, GXcopy);
          
	line_style = LineSolid;
             
	/* set line attributes */

	XSetLineAttributes(event->display, gc, 
        line_width, line_style, cap_style, join_style);
             
	XSetForeground(event->display, gc, fill_pixel);

             
	XDrawLine(event->display, event->window, gc, 
		  x, y, event->x, y);
	XDrawLine(event->display, event->window, gc, 
		  event->x, y, event->x, event->y);
	XDrawLine(event->display, event->window, gc, 
		  event->x, event->y, x, event->y);
	XDrawLine(event->display, event->window, gc, 
		  x, event->y, x, y);
            
	width = event->x - x;
	height = event->y - y;
	XFillRectangle(event->display, event->window, 
		       gc, x, y, width, height);
      }
  last_x = event->x;
  last_y = event->y;
}



michel.buffa@essi.fr