pvbrowser, pvb® - The Process Visualiation Browser.

  • English
  • Deutsch

Anatomy of a pvserver in C/C++

Within the introduction we have already seen a pvserver in C/C++. Now let us review the sourcecode of an example pvserver. We use the pvserver in pvbaddon/templates/weblayout for this review.

Note that the following sourcecode is not written manually. Instead this framework is automatically generated by the pvdevelop IDE. You may later add your own code within the framework but some parts will be updated by pvdevelop. Especially this applies to the design of the widgets.

main() program in main.cpp

With an #ifdef we decide if we want to use a multi threaded pvserver or use (x)inetd for running the pvserver. In case of the multi threaded pvserver we wait for clients with pvAccept(). Whenever a pvbrowser client connects we start a new thread for handling this client with pvCreateThread().

#ifdef USE_INETD
int main(int ac, char **av)
{
PARAM p;

  pvInit(ac,av,&p);
  /* here you may interpret ac,av and set p->user to your data */
  pvMain(&p);
  return 0;
}
#else  // multi threaded server
int main(int ac, char **av)
{
PARAM p;
int   s;

  pvInit(ac,av,&p);
  /* here you may interpret ac,av and set p->user to your data */
  while(1)
  {
    s = pvAccept(&p);
    if(s != -1) pvCreateThread(&p,s);
    else        break;
  }
  return 0;
}
#endif
    

Steps

pvMain() in main.cpp

Handling of an individual client starts in pvMain() which is called by pvCreateThread(). In an endless loop we call all the masks that we have designed. In this case there is only mask1. We show it by calling show_mask1(). When the client closes the connection our thread will be terminated.

int pvMain(PARAM *p)
{
int ret;

  pvSetCaption(p,"pvs");
  pvResize(p,0,1280,1024);
  //pvScreenHint(p,1024,768); // this may be used to automatically set the zoomfactor
  ret = 1;
  pvGetInitialMask(p);
  if(strcmp(p->initial_mask,"mask1") == 0) ret = 1;

  while(1)
  {
    if(trace) printf("show_mask%d\n", ret);
    switch(ret)
    {
      case 1:
        pvStatusMessage(p,-1,-1,-1,"mask1");
        ret = show_mask1(p);
        break;
      default:
        return 0;
    }
  }
}
    

show_mask1() in mask1.cpp

In show_mask1() we call defineMask() where the sourcecode of it is automatically generated when you graphically design the mask. After that slotInit() is called where you can initialze your DATA (user structure DATA defined in mask1_slots.h) and the mask. Then show_mask1() goes to an event loop and calls the according slotFunctions when an event is received from the pvbrowser client. If the client does not send any event a NULL_EVENT will be send. There you can do cyclic updating of your mask.

int show_mask1(PARAM *p)
{
  DATA d;
  char event[MAX_EVENT_LENGTH];
  char text[MAX_EVENT_LENGTH];
  char str1[MAX_EVENT_LENGTH];
  int  i,w,h,val,x,y,button,ret;
  float xval, yval;

  defineMask(p);
  //rlSetDebugPrintf(1);
  if((ret=slotInit(p,&d)) != 0) return ret;
  readData(&d); // from shared memory, database or something else
  showData(p,&d);
  while(1)
  {
    pvPollEvent(p,event);
    switch(pvParseEvent(event, &i, text))
    {
      case NULL_EVENT:
        readData(&d); // from shared memory, database or something else
        showData(p,&d);
        if((ret=slotNullEvent(p,&d)) != 0) return ret;
        break;
      case BUTTON_EVENT:
        if(trace) printf("BUTTON_EVENT id=%d\n",i);
        if((ret=slotButtonEvent(p,i,&d)) != 0) return ret;
        break;
      case BUTTON_PRESSED_EVENT:
      // snip ...
    

Generated sourcecode that defines mask1

The enum will list all widgets that have been designed. You use these names in your slotFunctions to address a widget. Then the toolTip and whatsThis properties of the widgets are listed. Also the widgetType is listed. In generated_defineMask() pvdevelop generates the necessary sourcecode to create the widget tree in the pvbrowser client.

// _begin_of_generated_area_ (do not edit -> use ui2pvc) -------------------

// our mask contains the following objects
enum {
  ID_MAIN_WIDGET = 0,
  upperWidget,
  leftWidget,
  centerWidget,
  rightWidget,
  obj1,
  iCenter,
  iUp,
  iDown,
  iLeft,
  iRight,
  sliderZoom,
  layout1,
  layout2,
  ID_END_OF_WIDGETS
};

  static const char *toolTip[] = {
  "",
  "",
  "",
  "",
  "",
  "",
  "",
  "",
  "",
  "",
  "",
  "",
  ""};

  static const char *whatsThis[] = {
  "",
  "",
  "",
  "test.svg",
  "",
  "",
  "1center.png",
  "1uparrow.png",
  "1downarrow.png",
  "1leftarrow.png",
  "1rightarrow.png",
  "",
  ""};

  static const int widgetType[ID_END_OF_WIDGETS+1] = {
  0,
  TQTextBrowser,
  TQTextBrowser,
  TQDraw,
  TQGroupBox,
  TQLabel,
  TQImage,
  TQImage,
  TQImage,
  TQImage,
  TQImage,
  TQSlider,
  -1 };

static int generated_defineMask(PARAM *p)
{
  int w,h,depth;

  if(p == NULL) return 1;
  w = h = depth = strcmp(toolTip[0],whatsThis[0]);
  if(widgetType[0] == -1) return 1;
  if(w==h) depth=0; // fool the compiler
  pvStartDefinition(p,ID_END_OF_WIDGETS);

  pvQTextBrowser(p,upperWidget,0);
  pvSetGeometry(p,upperWidget,5,5,945,200);
  pvSetFont(p,upperWidget,"Sans Serif",10,0,0,0,0);
  pvSetMinSize(p,upperWidget,0,200);
  pvSetMaxSize(p,upperWidget,99999,200);

  pvQTextBrowser(p,leftWidget,0);
  pvSetGeometry(p,leftWidget,5,215,175,445);
  pvSetFont(p,leftWidget,"Sans Serif",10,0,0,0,0);
  pvSetMaxSize(p,leftWidget,200,99999);

  pvQDraw(p,centerWidget,0);
  pvSetGeometry(p,centerWidget,190,215,580,425);
  pvSetFont(p,centerWidget,"Sans Serif",10,0,0,0,0);
  pvSetWhatsThis(p,centerWidget,"test.svg");
  pvSetMinSize(p,centerWidget,100,100);

  pvQGroupBox(p,rightWidget,0,-1,HORIZONTAL,"Tools");
  pvSetGeometry(p,rightWidget,780,210,170,450);
  pvSetFont(p,rightWidget,"Sans Serif",10,0,0,0,0);
  pvSetMaxSize(p,rightWidget,200,99999);

  pvQLabel(p,obj1,rightWidget);
  pvSetGeometry(p,obj1,10,150,145,35);
  pvSetText(p,obj1,"Put your tools here");
  pvSetFont(p,obj1,"Sans Serif",10,0,0,0,0);

  pvDownloadFile(p,"1center.png");
  pvQImage(p,iCenter,rightWidget,"1center.png",&w,&h,&depth);
  pvSetGeometry(p,iCenter,35,65,22,22);
  pvSetFont(p,iCenter,"Sans Serif",10,0,0,0,0);
  pvSetWhatsThis(p,iCenter,"1center.png");

  pvDownloadFile(p,"1uparrow.png");
  pvQImage(p,iUp,rightWidget,"1uparrow.png",&w,&h,&depth);
  pvSetGeometry(p,iUp,35,40,22,22);
  pvSetFont(p,iUp,"Sans Serif",10,0,0,0,0);
  pvSetWhatsThis(p,iUp,"1uparrow.png");

  pvDownloadFile(p,"1downarrow.png");
  pvQImage(p,iDown,rightWidget,"1downarrow.png",&w,&h,&depth);
  pvSetGeometry(p,iDown,35,90,22,22);
  pvSetFont(p,iDown,"Sans Serif",10,0,0,0,0);
  pvSetWhatsThis(p,iDown,"1downarrow.png");

  pvDownloadFile(p,"1leftarrow.png");
  pvQImage(p,iLeft,rightWidget,"1leftarrow.png",&w,&h,&depth);
  pvSetGeometry(p,iLeft,10,65,22,22);
  pvSetFont(p,iLeft,"Sans Serif",10,0,0,0,0);
  pvSetWhatsThis(p,iLeft,"1leftarrow.png");

  pvDownloadFile(p,"1rightarrow.png");
  pvQImage(p,iRight,rightWidget,"1rightarrow.png",&w,&h,&depth);
  pvSetGeometry(p,iRight,60,65,22,22);
  pvSetFont(p,iRight,"Sans Serif",10,0,0,0,0);
  pvSetWhatsThis(p,iRight,"1rightarrow.png");

  pvQSlider(p,sliderZoom,rightWidget,10,200,1,10,Vertical);
  pvSetGeometry(p,sliderZoom,125,30,25,100);
  pvSetFont(p,sliderZoom,"Sans Serif",10,0,0,0,0);

  pvQLayoutHbox(p,ID_MAIN_WIDGET,-1);

  pvQLayoutVbox(p,layout1,-1);

  pvQLayoutHbox(p,layout2,-1);

  pvAddWidgetOrLayout(p,ID_MAIN_WIDGET,layout1,-1,-1);
  pvAddWidgetOrLayout(p,layout1,upperWidget,-1,-1);
  pvAddWidgetOrLayout(p,layout1,layout2,-1,-1);
  pvAddWidgetOrLayout(p,layout2,leftWidget,-1,-1);
  pvAddWidgetOrLayout(p,layout2,centerWidget,-1,-1);
  pvAddWidgetOrLayout(p,layout2,rightWidget,-1,-1);

  pvEndDefinition(p);
  return 0;
}

// _end_of_generated_area_ (do not edit -> use ui2pvc) ---------------------
    

What we have to code ourself

Even the skeleton of this sourcecode was generated by pvdevelop when we created the mask. Our task has only been to code the content of the slotFunctions.

In our user defined structure DATA we have inserted a class from rllib which allows us to animate SVG graphics. The small helper function drawSVG1() has been written manually. The rest of the code shows the logic we have written.

typedef struct // (todo: define your data structure here)
{
  rlSvgAnimator svgAnimator;
}
DATA;

static int drawSVG1(PARAM *p, int id, DATA *d)
{
  if(d == NULL) return -1;
  if(d->svgAnimator.isModified == 0) return 0;
  gBeginDraw(p,id);
  d->svgAnimator.writeSocket();
  gEndDraw(p);
  return 0;
}

static int slotInit(PARAM *p, DATA *d)
{
  if(p == NULL || d == NULL) return -1;
  //memset(d,0,sizeof(DATA));

  // load HTML
  pvDownloadFile(p,"upperWidget.html");
  pvDownloadFile(p,"leftWidget.html");
  pvSetSource(p,upperWidget,"upperWidget.html");
  pvSetSource(p,leftWidget,"leftWidget.html");

  // load SVG
  d->svgAnimator.setSocket(&p->s);
  d->svgAnimator.setId(centerWidget);
  d->svgAnimator.read("test.svg");

  // keep aspect ratio of SVG
  pvSetZoomX(p, centerWidget, -1.0f);
  pvSetZoomY(p, centerWidget, -1.0f);

  // draw SVG
  drawSVG1(p,centerWidget,d);

  // download icons
  pvDownloadFile(p,"1center.png");
  pvDownloadFile(p,"1uparrow.png");
  pvDownloadFile(p,"1downarrow.png");
  pvDownloadFile(p,"1leftarrow.png");
  pvDownloadFile(p,"1rightarrow.png");
  pvDownloadFile(p,"1center2.png");
  pvDownloadFile(p,"1uparrow2.png");
  pvDownloadFile(p,"1downarrow2.png");
  pvDownloadFile(p,"1leftarrow2.png");
  pvDownloadFile(p,"1rightarrow2.png");

  // set sliderZoom to 100 percent
  pvSetValue(p,sliderZoom,100);

  pvHtmlOrSvgDump(p,upperWidget,"dump.html");
  pvClientCommand(p,"html","dump.html");
  return 0;
}

static int slotNullEvent(PARAM *p, DATA *d)
{
  if(p == NULL || d == NULL) return -1;
  return 0;
}

static int slotButtonEvent(PARAM *p, int id, DATA *d)
{
  if(p == NULL || id == 0 || d == NULL) return -1;
  if     (id == iCenter)
  {
    pvSetImage(p,iCenter,"1center2.png");
    d->svgAnimator.zoomCenter(1.0f);
    d->svgAnimator.setMouseXY0(0,0);
    d->svgAnimator.setXY0(0.0f,0.0f);
    d->svgAnimator.moveMainObject(0,0);
    drawSVG1(p,centerWidget,d);
    pvSetValue(p,sliderZoom,100);
  }
  else if(id == iUp)     
  {
    pvSetImage(p,iUp,"1uparrow2.png");
    d->svgAnimator.setMouseXY0(0,0);
    d->svgAnimator.moveMainObject(0,-DELTA);
    drawSVG1(p,centerWidget,d);
  }
  else if(id == iDown)   
  {
    pvSetImage(p,iDown,"1downarrow2.png");
    d->svgAnimator.setMouseXY0(0,0);
    d->svgAnimator.moveMainObject(0,DELTA);
    drawSVG1(p,centerWidget,d);
  }
  else if(id == iLeft)   
  {
    pvSetImage(p,iLeft,"1leftarrow2.png");
    d->svgAnimator.setMouseXY0(0,0);
    d->svgAnimator.moveMainObject(-DELTA,0);
    drawSVG1(p,centerWidget,d);
  }
  else if(id == iRight)  
  {
    pvSetImage(p,iRight,"1rightarrow2.png");
    d->svgAnimator.setMouseXY0(0,0);
    d->svgAnimator.moveMainObject(DELTA,0);
    drawSVG1(p,centerWidget,d);
  }
  return 0;
}

static int slotButtonPressedEvent(PARAM *p, int id, DATA *d)
{
  if(p == NULL || id == 0 || d == NULL) return -1;
  return 0;
}

static int slotButtonReleasedEvent(PARAM *p, int id, DATA *d)
{
  if(p == NULL || id == 0 || d == NULL) return -1;
  if     (id == iCenter) pvSetImage(p,iCenter,"1center.png");
  else if(id == iUp)     pvSetImage(p,iUp,    "1uparrow.png");
  else if(id == iDown)   pvSetImage(p,iDown,  "1downarrow.png");
  else if(id == iLeft)   pvSetImage(p,iLeft,  "1leftarrow.png");
  else if(id == iRight)  pvSetImage(p,iRight, "1rightarrow.png");
  return 0;
}

// snip ...
    

Scope of variables

The user defined structure DATA is private to a mask and the connected client. The instance of the structure is defined in show_mask1(). You can compare DATA to private variables in a C++ class although this is ANSI C technique.

If you need variables that are available in all masks and are private to a connected client you have to set PARAM p->user = your_data_for_all_masks; . The instance of your_data_for_all_masks must be defined within pvMain().

If you need variables that are global to all masks and clients you can define them on top of main.cpp as YOUR_TYPE your_data; and reference this data in all masks as extern YOUR_TYPE your_data;. Please keep in mind that you might have to use a mutex to control access to these variables while several threads for several clients are running.