/* V1.1, 18.06.93, mfr

   This is an example application which is dedicated for the communication
   with the daVinci graph visualization system. This application needs the 
   Sun XView window toolkit for compilation. The communication is done
   through the daVinci communication protocol. Refer to the user manual for
   further informations about the application connection and the communication
   protocol. 
   
   About a daVinci application and the communication protocol:
   ===========================================================
   A daVinci application is administrating a graph structure. daVinci could
   be understood as an abstract user interface of this application. In view
   of the fact that daVinci is nothing more than a representation layer, it 
   has no knowledge about the graph except the structural information and so
   it is unable to manipulate the graph structure.
   The application administrated graph is passed to daVinci to produce an 
   interactive visualization. After presenting the graph in a window daVinci 
   is reporting events like user selection of graph nodes and edges back to 
   the application. The application could interpret or ignore these events. 
   It could manipulate the graph if it would be necessary and then pass a new 
   version of the graph back to daVinci.
   Beside this, the application is able to extend daVinci's user interface by 
   application specific menus. daVinci informs the application, if one of 
   these menus is selected by the user. For getting additional informations
   from the user the application is able to trigger some simple user dialogs 
   in daVinci like input of a string or choice from two different alternatives.

   About the application connection:
   =================================
   The communication between the application and daVinci is done with two 
   pipes: one for the application commands (application -> daVinci) and one 
   for the answers of the visualization system (daVinci -> application).
   So daVinci is reading the commands from stdin and is writing the answers
   to stdout. The application should write commands on stdout and read the
   answers from stdin. The syntax of the commands sent to daVinci and the 
   answers could be found in the user manual.
   
   Either the application or daVinci can establish the connection. If you call
   an application from daVinci, then the following operations are executed:
   
   	1. forking a new child process
   	2. creating two pipes
   	3. redirecting the file descriptors of the child and hisself to the
   	   corresponding ends of the pipe (i.e. stdout of the application
   	   is connected with daVinci's stdin via the first pipe and
   	   stdin of the application is connected with daVinci's stdout via 
   	   the second pipe). 
   	4. calling the application from the forked child process 
   	
   About using this application:
   =============================
   In this application the user has two different text subwindows. In the first
   one he can write commands which are sent to stdout if he presses the 'Send 
   Message' button. In the second subwindow he can see the answers from 
   daVinci. These answers are taken from stdin. With this functionality the 
   example application could be called from daVinci. On the other side the user 
   can call daVinci from this application with the 'Connect daVinci' button. In 
   this case the application is doing the operations outlined above. You can
   find the corresponding code in the function connect_daVinci().
   
   About the user interface of this application:
   =============================================
   The OPEN LOOK user interface is written by using the object oriented XView 
   toolkit. Refer to the XView programming manual for further informations.

   About important things to know:
   ===============================
   The pipe communication of daVinci needs a line buffered Input/Output,
   i.e. the termination flag of each command or answer is the return character
   (ascii 10). If daVinci is calling the application from a child process, the
   IO of this application is not line buffered! We need an explicit setbuf() 
   in main() to do this.
   
   Each return inside a command is eliminated in the function send_message() 
   to avoid, that daVinci interprets this command part in front of the return 
   as a whole (faulty) command. It is also important, that there are no 
   whitespaces (space and tabulators) after the command which is sent to 
   daVinci, because it is not possible for us to consume these extra 
   characters. The effect is, that daVinci is blocked in these situations,
   because there are still characters to read from the pipe. The function
   send_message() has filter for consuming these whitespaces before the 
   command is send to daVinci. So:
   
   	>>> - AVOID RETURNS INSIDE A daVinci COMMAND
   	>>> - TERMINATE EACH COMMAND WITH A RETURN CHARACTER
   	>>> - AVOID SPACES AND TABULATORS AFTER THE COMMAND WHICH YOU INTEND
   	      TO SEND TO daVinci!!!
         
   XView needs a handler for the signal SIGCHLD. This is the function 
   sigchldcatcher(). Refer to the XView manual for further informations.
   
   The function destroy_func() is called when the user quits this application.
   destroy_func() is killing the forked child process (no, the child is still
   alive if the parent process dies in this case!).
*/ 

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <sys/ioctl.h>
#include <xview/xview.h>
#include <xview/frame.h>
#include <xview/panel.h>
#include <xview/textsw.h>
#include <xview/notify.h>
#include <xview/rect.h>
#include <xview/xv_xrect.h>

Frame 		frame,frame_connection;
Panel 		panel,panel2,panel_connection;
Panel_item	button_connect, button_send, button_quit, 
		headline_message, headline_answer,
		text_connection_path, text_connection_filename,
		button_connection;
Textsw		textsw_message, textsw_answer;
int		receiver_installed = 0;
int		process_id_child,
		some_width;
Rect		*rec1,*rec2;
char		*daVinci_Path,*daVinci_Filename;
FILE 		*fp;

void 		quit();
void		show_connection_subframe();
void 		connect_daVinci();
void 		send_message();
Notify_value 	stdin_notify();
Notify_value	destroy_func();
 
main (argc,argv)
    int argc;
    char *argv[];
/* This function is constructing the user interface by using XView functions.*/
{   setbuf(stdin,NULL);  	/* set stdin to 'line-buffered' IO */
    setbuf(stdout,NULL); 	/* set stdout to 'line-buffered' IO */
    xv_init(XV_INIT_ARGS, argc, argv, NULL);
				/* Create the root frame. */
    frame = (Frame)xv_create((Xv_object)XV_NULL, FRAME,
        FRAME_LABEL,    "Application",
        XV_WIDTH,       600,
        NULL);
        			/* Create a panel inside the  root frame. */
    panel = (Panel)xv_create(frame, PANEL, NULL);
    				/* Create the connect button on the panel. */
    button_connect = xv_create (panel, PANEL_BUTTON,
     	PANEL_LABEL_STRING,         "Connect daVinci...",
	PANEL_NOTIFY_PROC,          show_connection_subframe,
	NULL);
    				/* Create the send button on the panel. The
    				   function send_message() is called by the
    				   XView runtime system, if the user presses
    				   the button. */
    button_send = xv_create(panel, PANEL_BUTTON,
	PANEL_LABEL_STRING,         "Send Message",
	PANEL_NOTIFY_PROC,          send_message,
	NULL);
    				/* Create the quit button on the panel. */
    button_quit = xv_create(panel, PANEL_BUTTON,
	PANEL_LABEL_STRING,         "Quit",
	PANEL_NOTIFY_PROC,          quit,
	NULL);
				/* Create a message on the panel. */
    headline_message = xv_create(panel, PANEL_MESSAGE,
    	PANEL_NEXT_ROW,             -1,
    	PANEL_LABEL_STRING, 	    "Message to send:",
    	NULL);
    				/* Adjust panel height */
    window_fit_height(panel);	
    				/* Create the text subwindow for commands. */
    textsw_message = xv_create(frame, TEXTSW,
    	XV_HEIGHT,	200,
    	TEXTSW_IGNORE_LIMIT, TEXTSW_INFINITY,
    	NULL);
    				/* Create a second panel. */
    panel2 = (Panel)xv_create(frame, PANEL, NULL);
    				/* Create a message on the second panel. */
    headline_message = xv_create(panel2, PANEL_MESSAGE,
    	PANEL_LABEL_STRING, 	    "Answer from daVinci:",
    	NULL);
    				/* Ajust panel height. */
    window_fit_height(panel2);
    				/* Create the text subwindow for answers. */
    textsw_answer = xv_create(frame, TEXTSW,
    	XV_HEIGHT,	200,
    	TEXTSW_IGNORE_LIMIT, TEXTSW_INFINITY,
    	NULL);
    				/* Ajust frame width and height. */
    window_fit(frame);
    				/* Create a subframe for entering the path. */
    frame_connection = xv_create(frame,FRAME_CMD,
        FRAME_LABEL,	"Connect daVinci",
        NULL);
        			/* Create a panel on the subframe for entering
        			   the path and filename of daVinci. */
    panel_connection = xv_create(frame_connection,PANEL,
        XV_X,		0,
        XV_Y,		0,
        PANEL_LAYOUT,PANEL_VERTICAL,
        NULL);
        			/* Create a text input for daVinci's path. */
    text_connection_path = xv_create(panel_connection,PANEL_TEXT,
        XV_X,		20,
        XV_Y,		20,
        PANEL_LABEL_STRING, "Directory of daVinci:",
        PANEL_VALUE_DISPLAY_WIDTH, 400,
        NULL);
        			/* Create a text input for daVinci's filename.*/
    text_connection_filename = xv_create(panel_connection,PANEL_TEXT,
        PANEL_LABEL_STRING, 	"Filename of daVinci:",
        PANEL_VALUE, 		"daVinci",
        PANEL_VALUE_DISPLAY_WIDTH, 400,
        NULL);
        			/* Create a 'done' button. */
    button_connection = xv_create(panel_connection,PANEL_BUTTON,
        PANEL_ITEM_Y_GAP,	20,
        PANEL_LABEL_STRING,	"Connect daVinci",
        PANEL_NOTIFY_PROC,	connect_daVinci,
        NULL);
        			/* Adjust subframe... First get the width of
        			   the longest text input to align both text
        			   inputs vertically. */
    some_width = xv_get(text_connection_path,PANEL_LABEL_WIDTH);
    xv_set(text_connection_path,
    	PANEL_VALUE_X,some_width+20,
    	NULL);
    xv_set(text_connection_filename,
    	PANEL_VALUE_X,some_width+20,
    	NULL);
    				/* Then get the extension rectangle of one of
    				   the text inputs to adjust the width of the
    				   subframe. The width should be the right side
    				   of the text input (r_left + r_width) plus a 
    				   border of 20 pixel. */
    rec1 = (Rect *)xv_get(text_connection_path,XV_RECT);
    xv_set(frame_connection,
    	XV_WIDTH,rec1->r_left+rec1->r_width+20,
    	NULL);
    				/* Finally get the extension rectangle of the
    				   button to center it inside the subframe 
    				   and to adjust the height of the subframe.
    				   The height should be the bottom of the 
    				   button (r_top + r_height) plus a border of
    				   20 pixel. The second xv_set centers the
    				   button in the subframe. */
    rec2 = (Rect *)xv_get(button_connection,XV_RECT);
    xv_set(frame_connection,
    	XV_HEIGHT,rec2->r_top+rec2->r_height+20,
    	NULL);
    xv_set(button_connection,
    	XV_X,((rec1->r_left+rec1->r_width+40)/2)- /* Half of subframe width */
  	      ((rec2->r_left+rec2->r_width)/2),   /* Half of button width */
  	NULL);
				/* Set a stdin notify. This function is called
				   by the XView runtime system, if there are
				   characters to read from stdin. */
    notify_set_input_func(frame,stdin_notify,0);
    				/* Give the control to the XView runtime 
    				   system. */
    xv_main_loop(frame);
    				/* Exit after termination. */
    exit(0);
}

void
show_connection_subframe()
/* This function is showing the subframe, in which the user could specify the
 * path and the filename of daVinci. This function is called by the XView
 * runtime system, if the user presses the 'Connect daVinci' button. */
{   xv_set(frame_connection,
	XV_SHOW, TRUE,
	NULL);
} 

void
connect_daVinci()
/* This funtion is connecting daVinci by forking a child process, creating
 * two pipes for the transmission of commands to daVinci and the receiption of
 * answers from daVinci, redirecting the stdin, stdout and stderr of the parent
 * and the child process to the corresponding end of the pipe and calling
 * daVinci from the child process. */
{   char	        daVinci_Path_and_Filename[10000];
    int                 i;
    Notify_value        sigchldcatcher();
    int 		pipe_io[2][2]; /* see diagram */
/*	
 *                 [0]                           [1]
 *    child reads:  |========= pipe_io[0] ========| <- parent writes
 *   pipe_io[0][0]                                     pipe_io[0][1]
 *
 *    parent reads: |========= pipe_io[1] ========| <- child writes
 *   pipe_io[1][0]                                     pipe_io[1][1]
 *
 * The parent process reads the output of the child process by reading
 * pipe_io[1][0] because the child is writing to pipe_io[1][1].
 * The child process gets its input from pipe_io[0][0] because the
 * parent writes to pipe_io[0][1].  Thus, one process is reading from
 * one end of the pipe while the other is writing at the other end.
 */
 
			  /* If daVinci is already connected: do nothing. */
    if (receiver_installed)	        
        return;
    else 
    	receiver_installed = 1;
    			  /* Get the path and filename of daVinci from the
    			     text inputs of the subframe. */
    daVinci_Path = (char *) xv_get(text_connection_path,PANEL_VALUE);
    daVinci_Filename = (char *) xv_get(text_connection_filename,PANEL_VALUE);
    			  /* Construct the absolute filename. */
    strcpy(daVinci_Path_and_Filename,daVinci_Path);
    strcat(daVinci_Path_and_Filename,"/");
    strcat(daVinci_Path_and_Filename,daVinci_Filename);
    			  /* Check if the filename exists. Exit if not*/
    if ((fp=fopen(daVinci_Path_and_Filename,"r")) == NULL)
    {	printf("application: cannot find daVinci under %s\n",
    	       daVinci_Path_and_Filename);
    	return;
    };
    fclose(fp);
			  /* Disable the 'Connect daVinci' to avoid further
			   user selections. */
    xv_set(button_connect,PANEL_INACTIVE,TRUE,NULL);
    
    pipe(pipe_io[0]); 	  /* Create pipe for parent->child communication. */
    pipe(pipe_io[1]); 	  /* Create pipe for child->parent communication. */
    			  /* Fork child process. Up from here the code is
       			     executed from the child AND the parent process! */
    switch (process_id_child = fork())  
    {   case -1:	  /* Unable to fork process! Close both pipes. */
            close(pipe_io[0][0]);
            close(pipe_io[0][1]);
            close(pipe_io[1][0]);
            close(pipe_io[1][1]);
            perror("fork failed");
            exit(1);
        case  0: {	  /* This is program code for the child process! */
            		  /* Redirect child's stdin (0), stdout (1) and
            		     stderr (2) to the corresponding pipe ends. */
            		     
            struct rlimit r_limit;
            
            dup2(pipe_io[0][0], 0);
            dup2(pipe_io[1][1], 1);
            dup2(pipe_io[1][1], 2);
              		  /* Close all filedescriptors of the child process
            		     except stdin, stdout and stderr, because the
            		     child inherits the parents descriptors. */
            for (i = getdtablesize() /*getrlimit(RLIMIT_NOFILE,&r_limit)*/; i > 2; i--)                 
                (void) close(i);
                	  /* Reinstate all signals of the child process to
                	     their default value, because the child inherits 
                	     the parents signal settings. */
            for (i = 0; i < NSIG; i++)
                (void) signal(i, SIG_DFL);
                	  /* Start daVinci. */
	    execl(daVinci_Path_and_Filename,daVinci_Filename,"-pipe",0);
            		  /* Disaster has occured if we get here! */
            if (errno == ENOENT)
                printf("error: can't start programm 'receiver'.");
            else
                perror("error: problems with programm 'receiver'");
            _exit(-1);
          }
        default: 	  /* This is program code for the parent process! */
            		  /* Redirect parents's stdin (0), stdout (1) and
            		     stderr (2) to the corresponding pipe ends. */
            dup2(pipe_io[1][0], 0);
            dup2(pipe_io[0][1], 1);
            dup2(pipe_io[0][1], 2);
            		  /* Close unused portions of the pipes. */
            close(pipe_io[1][0]);
            close(pipe_io[0][1]);
            close(pipe_io[0][0]); 
            close(pipe_io[1][1]);
            		  /* Install necessary handler for signal SIGCHLD. */
            notify_set_wait3_func(frame, sigchldcatcher, process_id_child);
            		  /* Install function for destroying child process. */
            notify_interpose_destroy_func(frame,destroy_func);
    }
}

void
send_message()
/* This function is sending the whole content of the first text subwindow to 
 * stdout. If daVinci is connected, it will receive this command. Otherwise 
 * the command appears in the shell. The command should neighter contain any 
 * return character nor any appended whitespaces. These bad characters are 
 * eliminated to ensure a correct communication with daVinci. This function 
 * is called by the XView runtime system, if the user presses the 
 * 'Send Message' button. */
{   int 	  length;
    Textsw_index  next_pos;
    char 	  buffer[100000]; /* should be big enough ;-) */
    int		  i;

			/* Get lenght of text subwindow content. */
    length = xv_get(textsw_message,TEXTSW_LENGTH);
    			/* Get text subwindow content. */
    next_pos = (Textsw_index) 
      xv_get(textsw_message,TEXTSW_CONTENTS,0,buffer,length);
      			/* Terminate string with a NULL. */
    buffer[length] = 0;
    			/* Substitute each return character with a space. */
    for (i = 0; i <= length; i++)
        if (buffer[i] == '\n')
            buffer[i] = ' ';
            		/* Consume appended whitespaces. */ 
    i = length - 1;
    while((buffer[i] == ' ') || (buffer[i] == '\t'))
    {   buffer[i] = 0;
        i--;
    }			/* Write the daVinci command to stdout. stdout could 
    			   be redirected to the pipe if daVinci is connected.
    			   A return character is appended after the command
    			   to flush the write buffer. */
    printf("%s\n",buffer); 
} 

void
quit()
/* This function quits the application by destroying the root frame. This 
 * function is called by the XView runtime system, if the user presses the 
 * 'Quit' button. */
{   xv_destroy_safe(frame);
}

Notify_value stdin_notify(client,fd)
    Notify_client client;
    register int fd;
/* This function is reading a daVinci answer (if daVinci is connected) from
 * stdin. The answer is placed in the second text subwindow. This function is 
 * called by the XView runtime system, if the XView notifier mechanism detects 
 * an input on stdin. */
{   char input[BUFSIZ];
    char *input_ok;

			/* Disable the 'Connect daVinci' to avoid further
			   user selections. */    
    xv_set(button_connect,PANEL_INACTIVE,TRUE,NULL);
    			/* Read the daVinci answer from stdin. */
    input_ok = gets(input);
    			/* Put the answer into the second text subwindow. */
    if(input_ok==NULL)
    	{}
    else
    	{textsw_insert(textsw_answer,input,strlen(input));
    	 textsw_insert(textsw_answer,"\n",1);}
    return NOTIFY_DONE;
}

Notify_value destroy_func(client,status)
    Notify_client client;
    Destroy_status status;
/* This function is killing the forked child process if the parent process 
 * dies. This function is called by the XView runtime system. */
{   switch (status)
    {	case DESTROY_CHECKING:
    	    /* Nothing to do here, because this application is always saying 
    	       ok to be destroyed. */
        case DESTROY_SAVE_YOURSELF:
    	    /* Nothing to do here, because this application hasn't any 
    	       environment to save. */
    	case DESTROY_CLEANUP:
    	    /* Kill the forked child process (daVinci). */
    	    kill(process_id_child,15);
    	    return notify_next_destroy_func(client,status);
    	case DESTROY_PROCESS_DEATH:
    	    /* Kill the forked child process (daVinci). */
    	    kill(process_id_child,15);
    	default:
     	    /* Kill the forked child process (daVinci). */
    	    kill(process_id_child,15);
    };
    return NOTIFY_DONE;
}

Notify_value sigchldcatcher(client,pid,status,rusage)
    Notify_client client;
    int pid;
    int *status;
    struct rusage *rusage;
/* This functions handles the death of the child (daVinci). If the process 
 * dies, the child dies and generates a SIGCHLD signal. Capture it and disable 
 * the functions that talk to the pipes. This function is called by the XView
 * runtime system. */
{   if (WIFEXITED(*status))
    {   printf("Process terminated with status %d\n", *status);
        /* unregister input func with appropriate file descriptor */
        notify_set_input_func(client, stdin_notify,0);
        return NOTIFY_DONE;
    }
    puts("SIGCHLD not handled");
    return NOTIFY_IGNORED;
}



