Subject: v11i099: Graphics editor for Suns, Part03/06 Newsgroups: comp.sources.unix Sender: sources Approved: rs@uunet.UU.NET Submitted-by: steinmetz!sbcs!nyfca1!chan (Douglas Chan) Posting-number: Volume 11, Issue 99 Archive-name: graphedit/part03 [ Note the /tmp/catshar, and the way that "draw.c" is split into two files. Douglas did a nice packing jobs on this submission. --r$ ] # This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # # Contents: draw.c.shar2 /tmp/catshar echo x - draw.c.shar2 sed 's/^@//' > "draw.c.shar2" <<'@//E*O*F draw.c.shar2//' /* create segment for the text sample if it is not there */ if ( !(dlist=ge_display_list(GE_CHAR_SAMPLE)) ) { make_new_seg(GE_CHAR_SAMPLE); dlist=ge_display_list(GE_CHAR_SAMPLE); ge_graph(GE_CHAR_SAMPLE, GE_MOVE, TEXTX, TEXTY); ge_graph(GE_CHAR_SAMPLE, GE_TEXT, (float)((int)text_sample), 0.); } /* if */ /* change font of text sample and draw sample */ dlist->attr.font=value; attr_changed = 1; ge_draw(GE_CHAR_SAMPLE); } /* do_change_font */ /***************************************************************************** do_change_charsize is the notify procedure of the slider for character size. It will create the text sample if it is not there and update the size of the test sample according to the value of the slider. *****************************************************************************/ do_change_charsize(item, value, event) Panel_item item; int value; Event *event; { struct list *dlist; /* create segment for text sample if it is not there */ if ( !(dlist=ge_display_list(GE_CHAR_SAMPLE)) ) { make_new_seg(GE_CHAR_SAMPLE); dlist=ge_display_list(GE_CHAR_SAMPLE); ge_graph(GE_CHAR_SAMPLE, GE_MOVE, TEXTX, TEXTY); ge_graph(GE_CHAR_SAMPLE, GE_TEXT, (float)((int)text_sample), 0.); } /* change character size of text sample and draw sample */ dlist->attr.charsize=value; attr_changed = 1; ge_draw(GE_CHAR_SAMPLE); } /* do_change_charsize */ /***************************************************************************** do_change_linestyle is the notify procedure of the linestyle choice. It will create the line sample if it is not there and update the line style of the line sample according to the value chosen. *****************************************************************************/ /* define coordinates of line sample */ #define LINEX 80. #define LINEY 370. do_change_linestyle(item, value, event) Panel_item item; int value; Event *event; { struct list *dlist; /* create segment for line sample if it is not there */ if ( !(dlist=ge_display_list(GE_LINE_SAMPLE)) ) { make_new_seg(GE_LINE_SAMPLE); dlist=ge_display_list(GE_LINE_SAMPLE); ge_graph(GE_LINE_SAMPLE, GE_MOVE, LINEX, LINEY); ge_graph(GE_LINE_SAMPLE, GE_LINE, LINEX+60., LINEY); } /* update line style of line sample and draw sample */ dlist->attr.linestyle=value; attr_changed = 1; ge_draw(GE_LINE_SAMPLE); } /* do_change_linestyle */ /***************************************************************************** do_change_linewidth is the notify procedure of the slider for line widths. It will create the line sample if it is not there and update the line width of the line sample according to the value of the slider. *****************************************************************************/ do_change_linewidth(item, value, event) Panel_item item; int value; Event *event; { struct list *dlist; /* create segment for the line sample if necessary */ if ( !(dlist=ge_display_list(GE_LINE_SAMPLE)) ) { make_new_seg(GE_LINE_SAMPLE); dlist=ge_display_list(GE_LINE_SAMPLE); ge_graph(GE_LINE_SAMPLE, GE_MOVE, LINEX, LINEY); ge_graph(GE_LINE_SAMPLE, GE_LINE, LINEX+60., LINEY); } /* if */ /* update line width of line sample and draw sample */ dlist->attr.linewidth=value; attr_changed = 1; ge_draw(GE_LINE_SAMPLE); } /* do_change_linewidth */ /***************************************************************************** do_break is used to break a segment into segments with one primitive in each segment. It scans the display list of the segment looking for instructions such as GE_MOVE and GE_POLYGON which imply the beginning of a primitive and create a new segment for it. *****************************************************************************/ do_break() { int segnam; struct list *dlist; struct list_item *ptr; struct attr attr; int fill; do_message(""); /* ask for number of segment to break */ do_usage("Select segment","",""); if ( ! (segnam=ge_pick()) ) { do_usage("","",""); do_message("No segment selected."); return; } /* if */ do_usage("","",""); /* delete segment from view surface */ dlist = ge_display_list(segnam); attr = dlist->attr; attr.drawn = 0; dlist->attr.deleted=1; ge_draw(segnam); if ( ptr=dlist->d_list ) { ptr=ptr->next; segopen = FALSE; fill = 0; /* scan the display list to create the new segments */ do { switch ( ptr->instr ) { /* create new segment at GE_MOVE and GE_PLOT */ case GE_MOVE: case GE_PLOT: if ( segopen ) ge_draw(opensegment); make_new_seg(0); curr_dlist->attr=attr; break; case GE_POLYGON: /* create new segment at GE_POLYGON */ if ( segopen ) ge_draw(opensegment); make_new_seg(0); curr_dlist->attr=attr; case GE_CIRCLE: case GE_ELLIPSE: /* set up the proper fill instruction for polygon, circle and ellipse */ if (fill) { ge_graph(opensegment, GE_FILL, (float)fill, 0.); fill = 0; } break; /* no special action for these instructions */ case GE_LINE: case GE_POLYLINE: case GE_TEXT: case GE_CONT: case GE_ARC: case GE_ARCEXT: break; /* save the fill instruction value, which will be used by the next polygon, circle or ellipse */ case GE_FILL: fill = (int)ptr->x; break; /* save any change of attributes, which will be used as the initial attributes of the next created segment */ case GE_SETCOLOR: attr.color=(int)ptr->x; break; case GE_SETTYPELINE: attr.linestyle=(int)ptr->x; break; case GE_SETLINEWIDTH: attr.linewidth=(int)ptr->x; break; case GE_SETFONT: attr.font=(int)ptr->x; break; case GE_SETCHARSIZE: attr.charsize=(int)ptr->x; break; /* check that all possible instructions are properly handled */ default: fprintf(stderr, "Internal Error-Action for instruction %d missing.\n", ptr->instr); break; } /* switch */ /* add instruction to the current segment */ ge_graph(opensegment, ptr->instr, ptr->x, ptr->y); ptr=ptr->next; } while (ptr != dlist->d_list->next); } /* if */ /* draw the last segment created */ ge_draw(opensegment); /* delete the old segment */ ge_delete_segment(segnam); segopen = FALSE; /* repeat if the continous option is on */ if ( panel_get_value(cycle_cont) ) do_break(); } /* do_break */ /***************************************************************************** ge_newscale is used to determine the scaling factor for radius of circles or character size of text when a segment is joined to another segment. Input sx1, sy1 - x,y scale of segment to be joined sx2, sy2 - x,y scale of segment joining to the first segment Output Return the scaling factor determined. *****************************************************************************/ float ge_newscale(sx1, sy1, sx2, sy2) float sx1, sy1, sx2, sy2; { return (sqrt((double) ((sx2 * sx2 + sy2 * sy2)/ (sx1 * sx1 + sy1 * sy1)))); } /* ge_newscale */ /***************************************************************************** do_join is used to join a number of segments to form one segment. It will ask the user to pick the first segment. The display list of all the following segments picked will be appended to the display list of the first segment after any apporpiate changes. It will finish when the user do not pick any segment or pick the first segment. *****************************************************************************/ do_join() { int segnam, firstsegment; struct list *dlist, *first_dlist; struct list_item *ptr; struct attr attr; float sina, cosa; /* ask the user to pick the first segment */ do_message(""); do_usage("First segment","",""); if ( ! (firstsegment=ge_pick()) ) { /* abort if no segment picked */ do_usage("","",""); do_message("No segment selected."); return; } /* if */ /* get the display list of the first segment */ segopen = FALSE; first_dlist = ge_display_list(firstsegment); curr_dlist = first_dlist; attr = first_dlist->attr; /* update attr according to display list of first segment to obtain attributes at the end of the display list */ if ( ptr=first_dlist->d_list ) { ptr=ptr->next; do { switch ( ptr->instr ) { case GE_SETCOLOR: attr.color = (int) ptr->x; break; case GE_SETTYPELINE: attr.linestyle = (int) ptr->x; break; case GE_SETLINEWIDTH: attr.linewidth = (int) ptr->x; break; case GE_SETFONT: attr.font = (int) ptr->x; break; case GE_SETCHARSIZE: attr.charsize = (int) ptr->x; break; default: break; } /* switch */ ptr=ptr->next; } while ( ptr != first_dlist->d_list->next ); } /* if */ /* ask user to pick a number of segments to join */ do_message("Select first or no segment when done."); do_usage("Select segment", "", ""); /* join until user pick no/first segment */ while (((segnam=ge_pick()) && (segnam != firstsegment))) { float newscale; dlist = ge_display_list(segnam); newscale=ge_newscale(dlist->attr.sx, dlist->attr.sy, attr.sx, attr.sy); /* change character size of new segment according to the difference in scale between the two segments */ dlist->attr.charsize = (float)dlist->attr.charsize / newscale; /* change attributes of the first segment to the initial attributes of the joining segment */ if ( dlist->attr.color != attr.color ) { ge_graph(firstsegment, GE_SETCOLOR, (float)dlist->attr.color, 0.); attr.color = dlist->attr.color; } if ( dlist->attr.linestyle != attr.linestyle ) { ge_graph(firstsegment, GE_SETTYPELINE, (float)dlist->attr.linestyle, 0.); attr.linestyle = dlist->attr.linestyle; } if ( dlist->attr.linewidth != attr.linewidth ) { ge_graph(firstsegment, GE_SETLINEWIDTH, (float)dlist->attr.linewidth, 0.); attr.linewidth = dlist->attr.linewidth; } if ( dlist->attr.font != attr.font ) { ge_graph(firstsegment, GE_SETFONT, (float)dlist->attr.font, 0.); attr.font = dlist->attr.font; } if ( dlist->attr.charsize != attr.charsize ) { ge_graph(firstsegment, GE_SETCHARSIZE, (float)dlist->attr.charsize, 0.); attr.charsize = dlist->attr.charsize; } sina = sin((double)dlist->attr.ang); cosa = cos((double)dlist->attr.ang); /* append the display list of the joining segment to the display list of the first segment */ if ( ptr=dlist->d_list ) { ptr=ptr->next; do { float nx, ny, wx, wy; /* add item to segment */ switch ( ptr->instr ) { case GE_MOVE: case GE_LINE: case GE_PLOT: case GE_POLYLINE: case GE_POLYGON: case GE_CONT: /* transform point to world coordinate */ wx = dlist->attr.sx * cosa * ptr->x - dlist->attr.sy * sina * ptr->y + dlist->attr.tx; wy = dlist->attr.sx * sina * ptr->x + dlist->attr.sy * cosa * ptr->y + dlist->attr.ty; unxform(wx,wy,&nx,&ny); break; case GE_CIRCLE: case GE_ARCEXT: /* the radius has to be scaled according to the difference in scale */ nx = ptr->x / newscale; ny = ptr->y; break; case GE_ELLIPSE: /* scale height and width of ellipse according to the difference in scale */ nx = ptr->x * dlist->attr.sx / attr.sx; ny = ptr->y * dlist->attr.sy / attr.sy; break; case GE_ARC: /* change starting and ending angle of arc according to the difference in rotation */ nx = ptr->x + dlist->attr.ang - attr.ang; ny = ptr->y + dlist->attr.ang - attr.ang; break; case GE_TEXT: case GE_FILL: /* nothing has to be changed */ nx = ptr->x; ny = ptr->y; break; /* update current attribute of first segment */ case GE_SETCOLOR: attr.color = (int) ptr->x; nx = ptr->x; ny = ptr->y; break; case GE_SETTYPELINE: attr.linestyle = (int) ptr->x; nx = ptr->x; ny = ptr->y; break; case GE_SETLINEWIDTH: attr.linewidth = (int) ptr->x; nx = ptr->x; ny = ptr->y; break; case GE_SETFONT: attr.font = (int) ptr->x; nx = ptr->x; ny = ptr->y; break; case GE_SETCHARSIZE: /* character size has to be scaled according to difference in scale */ ptr->x /= newscale; attr.charsize = (int) ptr->x; nx = ptr->x; ny = ptr->y; break; /* check that all instuctions are properly handled */ default: fprintf(stderr, "Internal Error-Action for instruction %d missing.\n", ptr->instr); break; } /* switch */ /* add instruction to first segment */ ge_graph(firstsegment, ptr->instr, nx, ny); ptr=ptr->next; } while ( ptr != dlist->d_list->next ); } /* if */ /* delete the segment */ dlist->attr.deleted = 1; ge_draw(segnam); ge_delete_segment(segnam); /* redraw first segment */ ge_draw(firstsegment); } /* while */ do_message(""); do_usage("","",""); } /* do_join */ /***************************************************************************** do_clear will ask for confirmation of the clear and delete all segments (except the samples) from the display list and the view surface upon confirmation. *****************************************************************************/ do_clear() { int i, tmp; extern int ge_seglist; extern struct list ge_open[]; do_message(""); if (!(wmgr_confirm(basefd,"Press the left mouse button to confirm Clear. To cancel, press the right mouse button now."))) { /* clear is cancelled */ do_message("Cancelled."); return; } /* if */ /* scan the segment list to delete all segments */ for ( i=ge_seglist; i; i=tmp ) { tmp = ge_open[i].next; /* samples are excluded */ if ( ge_open[i].segno >= GE_MIN_SEG ) { /* remove segment from display */ ge_open[i].attr.deleted=1; ge_draw(ge_open[i].segno); /* remove segment from segment list */ ge_delete_segment(ge_open[i].segno); } /* if */ } /* for */ segopen=FALSE; } /* do_clear */ /***************************************************************************** do_copy_segment is used to make a copy of a segment. It will ask the user to pick a segment, create a new segment and copy the display list of the old segment to the new segment, and ask the user to move the new segment to its proper position. *****************************************************************************/ do_copy_segment() { int segnam, butnum; float tx0, ty0, tx, ty, x, y, wx, wy; struct list *dlist,*newdlist; struct list_item *ptr; /* ask for number of segment to copy */ do_message(""); do_usage("Select segment","",""); if (segnam=ge_pick() ) { ge_locator(-1, &butnum, &wx, &wy); /* create new segment */ make_new_seg(0); segopen=FALSE; /* copy display list and attributes to new segment */ dlist = ge_display_list(segnam); newdlist = ge_display_list(opensegment); newdlist->attr = dlist->attr; newdlist->attr.drawn = 0; if ( ptr=dlist->d_list ) { ptr=ptr->next; do { ge_graph(opensegment, ptr->instr, ptr->x, ptr->y); ptr=ptr->next; } while ( ptr != dlist->d_list->next ); } /* draw new segment */ ge_draw(opensegment); /* ask to user to move the new segment */ tx0 = newdlist->attr.tx; ty0 = newdlist->attr.ty; do_message("Move the new segment to its location."); do_usage("", "", "Done"); do { float tx, ty; /* determine amount of movement */ ge_locator(-1,&butnum, &x, &y); tx = x-wx; ty = y-wy; ge_align(&tx,&ty); dlist->attr.tx = tx0 + tx; dlist->attr.ty = ty0 + ty; /* redraw both segments */ ge_redisplay(newdlist); ge_redisplay(dlist); } while ( butnum != 3 ); segopen = FALSE; do_message(""); } else { do_message("No segment selected."); } do_usage("","",""); /* repeat if the continous option is on */ if ( segnam && panel_get_value(cycle_cont) ) do_copy_segment(); } /* do_copy_segment */ /***************************************************************************** do_open_file is used to open a file with its name in the file name field on the control pad. It will check whether the file name is typed in and ask the user to confirm if the file already exist. Output Return stream of opened file if the open is successful, and 0 otherwise. *****************************************************************************/ FILE *do_open_file() { char *string; FILE *file; /* get file name in file field on control pad */ string=(char *)panel_get_value(text_filen); /* check for empty file name */ if ( string[0] == '\0' ) { do_message("Empty file name."); return(0); } /* ask user to confirm if file exist */ if ( !(access(string, F_OK) || wmgr_confirm(basefd,"Press the left mouse button to remove file. To cancel, press the right mouse button now.")) ) { do_message("Cancelled."); return(0); } /* try to open the file */ if ( !(file=fopen(string, "w+")) ) { do_message("Cannot open file."); return(0); } return(file); } /* do_open_file */ /***************************************************************************** do_save_seg will ask the user to pick a segment to save and save it in the file indicated in the file field on the control pad. *****************************************************************************/ do_save_seg() { extern int errno; FILE *onfile; int segno; /* ask the user to pick a segment */ do_message(""); do_usage("Select segment","",""); if ( (segno=ge_pick()) < GE_MIN_SEG ) { do_usage("","",""); do_message("No segment selected."); return; } do_usage("","",""); /* try to open file */ if ( !(onfile=do_open_file()) ) { /* abort if file cannot be openned */ return; } /* write segment to file */ ps_write_seg(onfile,segno); /* close file */ fclose(onfile); do_message("Segment saved."); } /* do_save_seg */ /***************************************************************************** do_restore will load a segment from the file indicated in the file field on the control pad. *****************************************************************************/ do_restore_seg() { char *string; extern int errno; FILE *onfile; /* get file name from file field on control pad */ string=(char *)panel_get_value(text_filen); /* try to open file */ if (!(onfile=fopen(string, "r"))) { do_message("Cannot open file."); return; } /* load segment */ if ( ! ps_load(onfile) ) { do_message("Segment loaded."); } else { do_message("File is not a saved segment."); } /* close file */ fclose(onfile); segopen = FALSE; } /* do_restore_seg */ /***************************************************************************** do_save_pic will save the picture in the file indicated in the file field on the control pad. It will run the optimize routine to optimize the display list if the optimize option is on. *****************************************************************************/ do_save_pic() { FILE *onfile; extern Panel_item cycle_optm; /* try to open file in the file field */ if ( !(onfile=do_open_file()) ) { return; } /* run the optimize routine to optimize the display list if the optimize option is on */ if ( (int)panel_get_value(cycle_optm) ) { optimize(); } /* save the picture */ ps_write(onfile); /* close the file */ fclose(onfile); do_message("Saved."); } /* do_save_pic */ /***************************************************************************** do_restore_pic will load the picture from the file indicated in the file name field on the control pad. *****************************************************************************/ do_restore_pic() { char *string; extern int errno; FILE *onfile; /* get the file indicated in the file field */ string=(char *)panel_get_value(text_filen); if (!(onfile=fopen(string, "r"))) { do_message("Cannot open file."); return; } /* load picture */ if ( ! ps_load(onfile) ) { do_message("Picture loaded."); segopen = FALSE; } else { do_message("File is not a saved picture."); } /* close file */ fclose(onfile); } /* do_restore_pic */ /***************************************************************************** do_raster will write the pixrect image of whole/part of the picture on the already opened onfile. It will remove the samples and grid from the view surface and refresh the view surface before getting the pixrect image. Input onfile - opened stream for writing *****************************************************************************/ do_raster(onfile) FILE *onfile; { float xmin, ymin, xmax, ymax; int butnum,i; extern Panel_item cycle_raster; /* display list of samples */ struct { int deleted; struct list *dlist; } samples[4]; /* segments which should be invisible on the raster file */ static int sampleseg[] = {GE_COLOR_SAMPLE,GE_LINE_SAMPLE, GE_CHAR_SAMPLE, GE_GRID}; /* set region to dump */ xmin=0.; ymin=0.; if ( (int)panel_get_value(cycle_raster) ) { /* ask for region to dump when raster option is on */ do_usage("Reset corner","","Done"); do { xmax=xmin; ymax=ymin; ge_locator(6, &butnum, &xmax, &ymax); if ( butnum == 1 ) { /* reset corner */ xmin=xmax; ymin=ymax; } } while ( butnum != 3 ); do_usage("","",""); } else { /* dump the whole view surface */ xmax=GE_WIN_X; ymax=GE_WIN_Y; } /* remove the samples from the view surface */ for ( i=0; i<4; i++ ) { if ( (samples[i].dlist = ge_display_list(sampleseg[i])) && ! (samples[i].deleted = samples[i].dlist->attr.deleted)){ samples[i].dlist->attr.deleted = 1; ge_draw(sampleseg[i]); } /* if */ } /* for */ /* refresh the screen */ ge_refresh(); /* dump the region to onfile */ core_write(onfile, xmin, ymin, xmax, ymax); /* redraw the samples and grid */ for ( i=0; i<4; i++ ) { if ( samples[i].dlist && (!samples[i].deleted) ) { samples[i].dlist->attr.deleted = 0; ge_draw(sampleseg[i]); } /* if */ } /* for */ } /* do_raster */ /***************************************************************************** do_output will write the picture in the format specified by the output format option to the file indicated in the file field on the control pad. It will try to open the file, and then call the corresponding routine to write the picture. *****************************************************************************/ do_output() { extern Panel_item cycle_output; int format; FILE *onfile; FILE savestdout; do_message(""); /* open the file indicated in the file field on the control pad */ if ( !(onfile=do_open_file()) ) { return; } /* if */ /* get the format from the output format option */ format=(int)panel_get_value(cycle_output); switch ( format ) { case 0: /* raster file */ do_raster(onfile); break; case 1: /* pic */ pic_write(onfile); fclose(onfile); break; case 2: /* plot */ /* make onfile the standard output file so that the plot library will write to onfile */ savestdout = *stdout; *stdout = *onfile; plt_write(); /* reset standard output */ *stdout=savestdout; break; } /* switch */ do_message("Written"); } /* do_output */ /***************************************************************************** do_global_scale will scale and move the whole picture within a rectangular region specified by the user. *****************************************************************************/ do_global_scale() { float xmin, ymin, xmax, ymax; float tmp, sx, sy, tx, ty; int butnum,i; /* ask for the region to scale to */ do_message(""); do_usage("Reset corner","Cancel","Done"); xmin=0.; ymin=0.; do { xmax=xmin; ymax=ymin; ge_locator(6, &butnum, &xmax, &ymax); if ( butnum == 1 ) { /* reset corner */ xmin=xmax; ymin=ymax; } if ( butnum == 2 ) { /* cancel scale */ do_usage("","",""); do_message("Cancelled."); return; } } while ( butnum != 3 ); do_usage("","",""); /* get lower left corner in (xmin,ymin) and upper right corner in (xmax,ymax) */ if ( xmin > xmax ) { tmp=xmin; xmin=xmax; xmax=tmp; } if ( ymin > ymax ) { tmp=ymin; ymin=ymax; ymax=tmp; } /* determine the scale and movement */ sx = (xmax - xmin)/GE_WIN_X; sy = (ymax - ymin)/GE_WIN_Y; tx = xmin; ty = ymin; /* scan the segment list to scale all segments except the samples */ for ( i=ge_seglist; i; i=ge_open[i].next ) { if ( ge_open[i].segno >= GE_MIN_SEG ) { float sina, cosa, nx, ny; sina=sin((double)ge_open[i].attr.ang); cosa=cos((double)ge_open[i].attr.ang); /* determine the new translate value, scaling at (0,0) in world coordinate */ nx = (-ge_open[i].attr.tx*cosa - ge_open[i].attr.ty*sina)/ ge_open[i].attr.sx; ny = (-ge_open[i].attr.ty*cosa + ge_open[i].attr.tx*sina)/ ge_open[i].attr.sy; ge_open[i].attr.tx += nx*cosa*ge_open[i].attr.sx*(1-sx) - ny*sina*ge_open[i].attr.sy*(1-sy) + tx; ge_open[i].attr.ty += nx*sina*ge_open[i].attr.sx*(1-sx) + ny*cosa*ge_open[i].attr.sy*(1-sy) + ty; /* determine the new scale factor */ ge_open[i].attr.sx *= sx; ge_open[i].attr.sy *= sy; /* redraw segment */ ge_redisplay(&(ge_open[i])); } /* if */ } /* for */ } /* do_global_scale */ @//E*O*F draw.c.shar2// chmod u=rw,g=r,o=r draw.c.shar2 echo x - /tmp/catshar sed 's/^@//' > "/tmp/catshar" <<'@//E*O*F /tmp/catshar//' #!/bin/csh # # This script combine draw.c.shar1 and draw.c.shar2 together if ( -f draw.c.shar1 && -f draw.c.shar2 ) then echo 'Reconstruct draw.c from draw.c.shar1 and draw.c.shar2...' cat draw.c.shar1 draw.c.shar2 >> draw.c /bin/rm -f draw.c.shar1 draw.c.shar2 endif exit 0 @//E*O*F /tmp/catshar// chmod u=rwx,g=rx,o=rx /tmp/catshar echo Inspecting for damage in transit... temp=/tmp/shar$$; dtemp=/tmp/.shar$$ trap "rm -f $temp $dtemp; exit" 0 1 2 3 15 cat > $temp <<\!!! 1013 3155 28120 draw.c.shar2 9 38 275 catshar 1022 3193 28395 total !!! wc draw.c.shar2 /tmp/catshar | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp if [ -s $dtemp ] then echo "Ouch [diff of wc output]:" ; cat $dtemp else echo "No problems found." fi /tmp/catshar /bin/rm -f /tmp/catshar exit 0