home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / x / volume8 / wscrawl / part04 < prev    next >
Internet Message Format  |  1990-07-15  |  38KB

  1. Path: uunet!samsung!munnari.oz.au!metro!sunaus.oz!newstop!sun!hpcvlx.cv.hp.com
  2. From: brianw@hpcvlx.cv.hp.com (Brian Wilson)
  3. Newsgroups: comp.sources.x
  4. Subject: v08i056: wscrawl, Part04/05
  5. Message-ID: <138941@sun.Eng.Sun.COM>
  6. Date: 15 Jul 90 18:57:31 GMT
  7. Sender: news@sun.Eng.Sun.COM
  8. Lines: 1047
  9. Approved: argv@sun.com
  10.  
  11. Submitted-by: Brian Wilson <brianw@hpcvlx.cv.hp.com>
  12. Posting-number: Volume 8, Issue 56
  13. Archive-name: wscrawl/part04
  14.  
  15. #! /bin/sh
  16. # This is a shell archive.  Remove anything before this line, then feed it
  17. # into a shell via "sh file" or similar.  To overwrite existing files,
  18. # type "sh file -c".
  19. # The tool that generated this appeared in the comp.sources.unix newsgroup;
  20. # send mail to comp-sources-unix@uunet.uu.net if you want that tool.
  21. # If this archive is complete, you will see the following message at the end:
  22. #        "End of archive 4 (of 5)."
  23. # Contents:  wscrawl/xaa
  24. # Wrapped by argv@turnpike on Sun Jul 15 11:47:12 1990
  25. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  26. if test -f 'wscrawl/xaa' -a "${1}" != "-c" ; then 
  27.   echo shar: Will not clobber existing file \"'wscrawl/xaa'\"
  28. else
  29. echo shar: Extracting \"'wscrawl/xaa'\" \(35046 characters\)
  30. sed "s/^X//" >'wscrawl/xaa' <<'END_OF_FILE'
  31. X/*
  32. X *                                WSCRAWL
  33. X *
  34. X *  This file is: wscrawl.c 
  35. X *         ("image_f_io.c" and "pause_curs.h" are also part of this program)
  36. X *
  37. X *  This is the source code to the program "wscrawl".  The word "wscrawl"
  38. X *  stands for "window-scrawl", reflecting the history of wscrawl.  The user
  39. X *  may think of wscrawl as a paint program shared by any number of people
  40. X *  at the same time.  When wscrawl is run, it opens a separate window on 
  41. X *  each participant's display.  From that point, each participant may draw
  42. X *  in his or her window.  All participants see everything drawn by the other
  43. X *  participants instantly.
  44. X * 
  45. X *  NOTES OF PORTABILITY: The routine "block_until_input()" uses a "select"
  46. X *  system call.  Some systems do not have this call.  To patch for this
  47. X *  problem, simply comment out all source inside that routine.  The other
  48. X *  potential problem is with the "rand" function.  If the airbrush is not
  49. X *  "scattering" properly, then take a look at the function "my_rand" and
  50. X *  try to get it to return a random decimal between 0 and 1.
  51. X *
  52. X *  Feel free to use/change/modify/erase/whatever this source code.  Comments
  53. X *  requests, hacked up versions of wscrawl should go to: brianw@cv.hp.com
  54. X *
  55. X *  This program was written by Brian Wilson of Hewlett Packard Co.
  56. X *  Email: brianw@cv.hp.com
  57. X *  
  58. X *  To compile: "cc -o wscrawl wscrawl.c image_f_io.c -lX11"
  59. X *  To invoke: "wscrawl -d displayname1 -d displayname2 . . ."
  60. X *  For a complete usage message, just do a: "wscrawl -help"
  61. X */
  62. X
  63. X#define INTERESTING_EVENTS ButtonPressMask | ButtonReleaseMask | KeyPressMask \
  64. X               | StructureNotifyMask | ExposureMask
  65. X
  66. X#define WSCRAWL_WIN_WIDTH       760     /*initial window dimensions*/
  67. X#define WSCRAWL_WIN_HEIGHT       550
  68. X#define DIALOG_WIN_WIDTH       300 
  69. X#define DIALOG_WIN_HEIGHT       50
  70. X
  71. X#define MAX_NUM_DISPS       10      /*general global variables*/
  72. X#define NUM_OF_MENUS        7
  73. X#define FIXED_CHAR_WIDTH    7
  74. X#define TRUE            1
  75. X#define FALSE             0
  76. X
  77. X#define STRAIGHT_LINE        0       /*types of shapes*/
  78. X#define OUTLINE_RECT        1
  79. X#define FILLED_RECT        2
  80. X#define OUTLINE_OVAL        3
  81. X#define FILLED_OVAL        4
  82. X
  83. X#define NOT_PRESSED         0       /*Pointer States*/
  84. X#define PRESSED            1
  85. X#define IN_MENU            2
  86. X#define BETWEEN_MENUS           3
  87. X
  88. X#define SCRAWLING        1       /*modes of drawing*/
  89. X#define AIRBRUSHING        2
  90. X#define TYPING            3
  91. X#define ERASING            4
  92. X#define SELECTING_AN_AREA    5
  93. X#define RUBBER_POINTING        6
  94. X#define PLACING_A_BITMAP    7
  95. X#define PLACING_A_TEXTFILE    8
  96. X#define RESPONDING_TO_DIALOG    9
  97. X#define PLACING_AN_IMAGE    10
  98. X
  99. X#define SAVE_BITMAP        1       /*things to do after answering dialog*/
  100. X#define READ_IN_BITMAP        2
  101. X#define ADD_A_DISPLAY        3
  102. X#define READ_TEXTFILE        4
  103. X#define SAVE_IMAGE        5
  104. X#define READ_IN_IMAGE        6
  105. X#define DRAW_SHAPE        7       /*this is a hack to allow me use of the 
  106. X                      rubber select box*/
  107. X
  108. X#define MENU_ITEM_WIDTH            104     /*menu parameters*/
  109. X#define MENU_ITEM_HEIGHT    25
  110. X#define NO_ITEM_SELECTED    -1
  111. X
  112. X#define MAX_RAND      ((1<<15) - 1)
  113. X
  114. X#include <stdio.h>        /*standard i/o functions*/
  115. X#include <stdlib.h>        /*for the "getenv" command*/
  116. X#include <X11/Xos.h>            /*"time.h" necessary for the "select" stuff */
  117. X#include <X11/Xlib.h>        /*standard x defs*/
  118. X#include <X11/Xutil.h>        /*necessary for the XWMhints stuff*/
  119. X#include <X11/cursorfont.h>    /*we are gonna change the pointer*/
  120. X#include "pause_curs.h"        /*our custom pause cursor*/
  121. X
  122. Xstatic char what[] = "@(#)WSCRAWL - Brian Wilson 7/14/90";
  123. X
  124. Xfloat my_rand();                /*hack, hack, cough, wheez, sputter, hack*/
  125. X
  126. XXSetWindowAttributes menuwinvals =  /*struct for the menu windows*/
  127. X{
  128. X    None,                       /*default background pixmap*/
  129. X    1,                          /*background pixel*/
  130. X    CopyFromParent,             /*border_pixmap*/
  131. X    0, 0, 
  132. X    NorthWestGravity,           /*window gravity*/
  133. X    NotUseful,                  /*backing store*/
  134. X    0, 0, 0, 
  135. X    INTERESTING_EVENTS,         /*events we are interested in*/
  136. X    0, 
  137. X    True,                       /*override redirect flag*/
  138. X    0, 0
  139. X};
  140. X
  141. XXSetWindowAttributes winvals =  /*struct for the window*/
  142. X{
  143. X    None,                       /*default background pixmap*/
  144. X    1,                          /*background pixel*/
  145. X    CopyFromParent,             /*border_pixmap*/
  146. X    0, 0, 
  147. X    NorthWestGravity,           /*window gravity*/
  148. X    Always,                     /*backing store*/
  149. X    0, 0, 0, 
  150. X    INTERESTING_EVENTS,         /*events we are interested in*/
  151. X    0, 
  152. X    False,                      /*override redirect flag*/
  153. X    0, 0
  154. X};
  155. X
  156. XXGCValues menugcvalues =        /*menu graphics context values*/
  157. X{                               /*so we need a gc values structure*/
  158. X    GXxor,                       /*write rule*/
  159. X    0,0,0,0,0,0,
  160. X    0,0,0,0,0,0,0,0,0,          /*bunch of placeholders*/
  161. X    None,            /*subwindow_mode*/
  162. X    0,0,0,0,0,0
  163. X};
  164. X
  165. XXGCValues gcvalues =            /*We want to "include inferiors"*/
  166. X{                               /*so we need a gc values structure*/
  167. X    0,0,
  168. X    0,                          /*foreground color*/
  169. X    0,
  170. X    8,                          /*width of the line*/
  171. X    0,
  172. X    CapRound,                   /*CapRound looks good, but is VERY SLOW*/
  173. X    0,0,0,0,0,0,0,0,0,          /*bunch of placeholders*/
  174. X    None,            /*subwindow_mode*/
  175. X    0,0,0,0,0,0
  176. X};
  177. X
  178. XXGCValues cursor_gc_values =    /*We want the cursor to have XOR */
  179. X{                               /*so we need a gc values structure*/
  180. X    GXor, 
  181. X    0,0,0,
  182. X    4,                          /*Line width*/
  183. X    0,0,0,0,0,0,0,0,0,0,0,      /*bunch of placeholders*/
  184. X    0,0,0,0,0,0,0
  185. X};
  186. X
  187. X/*
  188. X * The following text definitions deserve some explanation.  If you want a new
  189. X * color, or font, or pen_width, simply add the desired value to these lists.
  190. X * The routines automatically incorporate these new values and change menu
  191. X * lengths.  If you want to add a brand new menu, then I recommend
  192. X * you add create a menu_text5 list, then link it appropriately in 
  193. X * "initialize_displays()", change the "#define NUM_OF_MENUS   7" to 8,
  194. X * and add the new functionality to the "menu_selection()" routine.  Good
  195. X * luck, as their are lots of little special cases to worry about, depending
  196. X * on your particular functionality.  Remember to mail interesting versions 
  197. X * to the above address.  :-)
  198. X */
  199. Xstatic char *menu_text0[] = {"Control", 
  200. X                 "Scrawl", 
  201. X                 "Airbrush", 
  202. X                 "Type", 
  203. X                 "Eraser",
  204. X                 "Draw Shapes",
  205. X                 "Rubber Pointer",
  206. X                 "Clear Windows", 
  207. X                 "--------------", 
  208. X                 "Add Display",
  209. X                 "Close Window", "****"};
  210. Xstatic char *menu_text1[] = {"PenColor", "white", "magenta", "red", "blue",
  211. X                 "cyan", "LightSteelBlue", "navy blue", "green",
  212. X                 "coral", "light grey", "orange", "plum", "yellow",
  213. X                 "black", "****"};
  214. Xstatic char *menu_text2[] = {"PenWidth", "1", "2", "4", "8", "15", "25", "30",
  215. X                 "45", "60", "100", "150", "500", "****"};
  216. Xstatic char *menu_text3[] = {"PenCapStyle", "CapRound", "CapButt", 
  217. X                 "CapNotLast", "CapProjecting", "****"};
  218. Xstatic char *menu_text4[] = {"Font", "fixed", "variable", 
  219. X                 "timR12", "helvO12", "courR12", "courR14",
  220. X                 "timBI18", "courR24", "timR24", "timBI24",
  221. X                 "ncenR24", "vbee-36", "vr-40", "vgl-40", 
  222. X                 "vsgn-57", "****"};
  223. Xstatic char *menu_text5[] = {"Shapes", "Straight Line", "Outline Rect", 
  224. X                 "Filled Rect", "Outline Oval", "Filled Oval", 
  225. X                 "****"};
  226. Xstatic char *menu_text6[] = {"File I/O", "Save Bitmap", "Read In Bitmap", 
  227. X                 "Read Text File", "Save Image", 
  228. X                 "Read In Image", "****"};
  229. Xchar **menu_text[NUM_OF_MENUS];
  230. X
  231. Xint num_of_disps = 1;               /*start with your home display*/
  232. Xchar disp_args[MAX_NUM_DISPS][50];  /*No more than 50 letters in display name*/
  233. Xchar PEN_COLOR[42];
  234. Xchar BACKGROUND_COLOR[42];
  235. Xchar MENU_HIGHLIGHT_COLOR[42];
  236. Xchar MENU_FOREGROUND_COLOR[42];
  237. Xchar MENU_BACKGROUND_COLOR[42];
  238. Xchar PEN_STYLE[42];
  239. Xchar FONT[256];
  240. Xint NUM_DOTS;
  241. Xint PEN_WIDTH;
  242. Xint CAP_STYLE;
  243. Xint NOTHING_DRAWN_YET;       /*I am so, so ashamed of this variable*/
  244. Xint TYPE_NOT_DRAW;
  245. X
  246. Xextern XImage *read_image_from_disk();
  247. X
  248. Xstruct char_node         /*for the history of typing (for backspacing)*/
  249. X{
  250. X    char the_char;
  251. X    int x, y;                /*where this letter was typed (only for <CR>)*/
  252. X    struct char_node *next;  /*pointer to the next character*/
  253. X};
  254. X
  255. Xstruct a_menu                 /*one of these for every menu*/
  256. X{
  257. X    Window win_id;         /*the window which IS the menu*/
  258. X    int num_items;           /*the number of items in this menu*/
  259. X    int item_selected;         /*the current item which is selected*/
  260. X    int checked_item;         /*the current item which is checked*/
  261. X};
  262. X
  263. Xstruct penwidthstruct        /*keeps track of all the current penwidths*/
  264. X{
  265. X    int scrawl;              /*the scrawl penwidth*/
  266. X    int airbrush;         /*the airbrush penwidth*/
  267. X    int eraser;                 /*the eraser penwidth*/
  268. X    int shape;                 /*the shape penwidth*/
  269. X};
  270. X
  271. Xstruct rubberpointstruct     /*this holds the rubberband cursor pixmap and dim*/
  272. X{
  273. X    Pixmap rubber_pointer_pix[MAX_NUM_DISPS]; /*a rubber pointer for each disp*/
  274. X    int width;               /*the width of the pointer*/
  275. X    int height;                 /*the height of the pointer*/
  276. X    int is_mapped_bool;         /*TRUE when this person's pointer is mapped*/
  277. X};
  278. X
  279. Xstruct disp_info_struct      /*one of these exists for every "wscrawl" window*/
  280. X{
  281. X    Display *disp;           /*the display variable for this scrawl window*/
  282. X    Window win_id;           /*the window id of this scrawl window*/
  283. X    Window status_win_id;    /*the status window id for this scrawl window*/
  284. X    Window eraser_win_id;    /*the eraser window id for this scrawl window*/
  285. X    Window dialog_win_id;    /*the dialog window id for this scrawl window*/
  286. X    GC *win_gc;              /*one graphics context for every scrawl window*/
  287. X    GC rubber_band_gc;       /*the graphics context for rubber band boxes*/
  288. X    GC fg_menu_gc;         /*the graphics context for the menu foreground*/
  289. X    GC hi_menu_gc;           /*the graphics context for highlighting of menus*/
  290. X    GC bg_menu_gc;         /*the graphics context for unhighlighting of mens*/
  291. X    int current_menu;         /*the current menu that is pulled down(if one is)*/
  292. X    struct a_menu menu[NUM_OF_MENUS]; /*each menu in a scrawl window*/
  293. X    int in_session_bool;     /*whether this win is still in the session or not*/
  294. X    int connection_num;      /*socket that this window is on*/
  295. X    int pointer_state;       /*choices include: NOT_PRESSED, PRESSED, IN_MENU*/
  296. X    int scrawl_mode;         /*choices include: SCRAWLING, AIRBRUSHING, TYPING*/
  297. X    int previous_scrawl_mode;/*choices include: SCRAWLING, AIRBRUSHING, TYPING*/
  298. X    int first_point_bool;    /*boolean: is this first point drawn in this win?*/
  299. X    int just_placed_something_bool; /*boolean: set if person placed bitmap,etc*/
  300. X    int dialog_what;         /*the thing we will do after user answers dialog*/
  301. X    char *dialog_text_prompt;/*the prompt that goes in the dialog box*/
  302. X    char dialog_text_return[256];/*text that the user has typed in dialog box*/
  303. X    int dialog_reply_index;  /*current location of the text in dialog_text_ret*/
  304. X    int capstyle;         /*pen width of this display*/
  305. X    int pen_width;         /*pen width of this display*/
  306. X    char pen_color_str[42];  /*pen color that this display is scrawling with*/
  307. X    char font_str[256];      /*font that this display is typing with*/
  308. X    int current_shape;         /*the current shape to be drawn (if one is)*/
  309. X    struct penwidthstruct pen_widths; /*all the various penwidths*/
  310. X    XFontStruct *the_font_struct;
  311. X    struct char_node *type_history; /*history of all typed characters*/
  312. X    GC cursor_gc;            /*graphics context for the typing cursor*/
  313. X    Cursor pause_cursor;     /*the pause cursor for this display*/
  314. X    struct rubberpointstruct rubber_pointer; /*the rubber pointer*/
  315. X    int prompt_width;         /*width of the typing cursor*/
  316. X    int orig_x, char_height; /*x origin and height of current font*/
  317. X    int cursor_on;         /*boolean indication of whether cursor is shown*/
  318. X    unsigned long background;/*planes mask of the background color*/
  319. X    XPoint select_start_pos; /*x,y of first click position of select area*/
  320. X    XPoint cur_pos;          /*current position of the typing cursor*/
  321. X    int rubber_band_width;   /*width of rubber band box for placing bitmap*/
  322. X    int rubber_band_height;  /*height of rubber band box for placing bitmap*/
  323. X    int win_width;           /*current width of this window*/
  324. X    int win_height;          /*current height of this window*/
  325. X    XPoint last_point;       /*the last point drawn in this window*/
  326. X    Atom xa_WM_DELETE_WINDOW;/*for communication with the window manager*/
  327. X    Atom xa_WM_PROTOCOLS;    /*for communication with the window manager*/
  328. X};
  329. X
  330. Xstruct disp_info_struct disp_info[MAX_NUM_DISPS];
  331. Xint num_people_drawing = 0;
  332. X
  333. X
  334. Xmain (argc, argv)
  335. Xint argc;
  336. Xchar *argv[];
  337. X{
  338. X    int i;
  339. X
  340. X    parse_command_line(argv, argc);
  341. X    initialize_displays(&num_people_drawing);  /*open displays, windows, etc*/
  342. X
  343. X    while (1)
  344. X    {      
  345. X    block_until_input();
  346. X
  347. X    do
  348. X    {
  349. X        for (i=0; i<num_of_disps; i++)
  350. X         if (disp_info[i].in_session_bool)
  351. X         {
  352. X                     XFlush(disp_info[i].disp);
  353. X                 process_event(i, &num_people_drawing);
  354. X             if (disp_info[i].in_session_bool == FALSE)
  355. X             break;
  356. X             switch (disp_info[i].pointer_state)
  357. X             {
  358. X                 case PRESSED:
  359. X                switch(disp_info[i].scrawl_mode)
  360. X                {
  361. X                case SELECTING_AN_AREA:
  362. X                    update_select_area(i);
  363. X                    break;
  364. X                case SCRAWLING:
  365. X                case AIRBRUSHING:
  366. X                case ERASING:
  367. X                case RUBBER_POINTING:
  368. X                            draw_on_screens(i); /*draw on ALL displays*/
  369. X                    break;
  370. X                default:
  371. X                    break;
  372. X                }
  373. X                break;
  374. X             case IN_MENU:
  375. X                update_menu_highlights(i);
  376. X                break;
  377. X             case NOT_PRESSED:
  378. X                switch (disp_info[i].scrawl_mode)
  379. X                {
  380. X                case TYPING:
  381. X                        type_on_screens(i);/*type on ALL displays*/
  382. X                    break;
  383. X                    case PLACING_A_TEXTFILE:
  384. X                    case PLACING_A_BITMAP:
  385. X                    case PLACING_AN_IMAGE:
  386. X                        slide_rubberband_box(i,&num_people_drawing);
  387. X                    break;
  388. X                    case RESPONDING_TO_DIALOG:
  389. X                        type_on_dialog(i);  /*type in dialog box*/ 
  390. X                    break;
  391. X                default:
  392. X                    break;
  393. X                }
  394. X                break;
  395. X             default:
  396. X                break;
  397. X             }
  398. X         }
  399. X    } while (num_people_drawing);    /*while someone is still drawing*/
  400. X    }
  401. X}
  402. X
  403. X
  404. X/*
  405. X * set_paused_cursors - this function sets all cursors on all displays to be
  406. X *            "Pause" cursors.  This function is called when some action is
  407. X *            done that totally sucks all cycles for a minute or two, like
  408. X *            reading an image in.
  409. X *             
  410. X */
  411. Xset_paused_cursors()
  412. X{
  413. X    int i, j;
  414. X
  415. X    for (i=0; i<num_of_disps; i++)
  416. X    {
  417. X        if (disp_info[i].in_session_bool) /*if window is alive*/
  418. X        {
  419. X            XDefineCursor(disp_info[i].disp, disp_info[i].win_id,
  420. X                         disp_info[i].pause_cursor);
  421. X            XDefineCursor(disp_info[i].disp, disp_info[i].status_win_id,
  422. X                         disp_info[i].pause_cursor);
  423. X            XDefineCursor(disp_info[i].disp, disp_info[i].eraser_win_id,
  424. X                         disp_info[i].pause_cursor);
  425. X            XDefineCursor(disp_info[i].disp, disp_info[i].dialog_win_id,
  426. X                         disp_info[i].pause_cursor);
  427. X            for (j=0; j<NUM_OF_MENUS; j++)
  428. X                XDefineCursor(disp_info[i].disp, disp_info[i].menu[j].win_id,
  429. X                         disp_info[i].pause_cursor);
  430. X            XFlush(disp_info[i].disp);
  431. X        }
  432. X    }
  433. X}
  434. X
  435. X
  436. X/*
  437. X * unset_paused_cursors - this function sets all cursors on all displays to be
  438. X *            the cursors they were before the "Pause" occured.  
  439. X */
  440. Xunset_paused_cursors()
  441. X{
  442. X    int i, j, the_cursor;
  443. X
  444. X    for (i=0; i< num_of_disps; i++)
  445. X    {
  446. X        if (disp_info[i].in_session_bool) /*if window is alive*/
  447. X        {
  448. X        switch (disp_info[i].scrawl_mode)
  449. X        { 
  450. X        case SCRAWLING:
  451. X            the_cursor = XC_dot;
  452. X            break;
  453. X        case AIRBRUSHING:
  454. X            the_cursor = XC_spraycan;
  455. X            break;
  456. X        case TYPING:
  457. X            the_cursor = XC_xterm;
  458. X            break;
  459. X        case ERASING:
  460. X            the_cursor = XC_draped_box;
  461. X            break;
  462. X        case RUBBER_POINTING:
  463. X            the_cursor = XC_target;
  464. X            break;
  465. X        case SELECTING_AN_AREA:
  466. X            the_cursor = XC_crosshair;
  467. X            break;
  468. X        case PLACING_A_BITMAP:
  469. X        case PLACING_A_TEXTFILE:
  470. X        case RESPONDING_TO_DIALOG:
  471. X        case PLACING_AN_IMAGE:
  472. X            the_cursor = XC_cross;
  473. X            break;
  474. X        default:
  475. X            break;
  476. X        }
  477. X        
  478. X        XDefineCursor(disp_info[i].disp, disp_info[i].win_id,
  479. X                 XCreateFontCursor(disp_info[i].disp, the_cursor));
  480. X        XDefineCursor(disp_info[i].disp, disp_info[i].status_win_id,
  481. X                 XCreateFontCursor(disp_info[i].disp, XC_star));
  482. X        XDefineCursor(disp_info[i].disp, disp_info[i].dialog_win_id,
  483. X                 XCreateFontCursor(disp_info[i].disp, XC_xterm));
  484. X        XDefineCursor(disp_info[i].disp, disp_info[i].eraser_win_id,
  485. X                 XCreateFontCursor(disp_info[i].disp, XC_draped_box));
  486. X            for (j=0; j<NUM_OF_MENUS; j++)
  487. X            XDefineCursor(disp_info[i].disp, disp_info[i].menu[j].win_id,
  488. X                 XCreateFontCursor(disp_info[i].disp, XC_right_ptr));
  489. X            XFlush(disp_info[i].disp);
  490. X        }
  491. X    }
  492. X}
  493. X
  494. X
  495. X/*
  496. X * place_a_textfile - this function places a text file onto the wscrawl
  497. X *               windows in the current font and color, at the location
  498. X *               this individual has just clicked.
  499. X */
  500. Xplace_a_textfile(disp_num, the_event)
  501. Xint disp_num;
  502. XXButtonPressedEvent *the_event;
  503. X{
  504. X    int i, c, length, top, left, width, height;
  505. X    FILE *text_file_ptr;
  506. X    char current_line[512];
  507. X
  508. X    (num_people_drawing)--;         /*done drawing now*/
  509. X
  510. X    /*
  511. X     * erase rubber band box
  512. X     */
  513. X    top = disp_info[disp_num].cur_pos.y - 
  514. X    disp_info[disp_num].rubber_band_height/2;
  515. X    left = disp_info[disp_num].cur_pos.x - 
  516. X                disp_info[disp_num].rubber_band_width/2;
  517. X    width = disp_info[disp_num].rubber_band_width;
  518. X    height = disp_info[disp_num].rubber_band_height;
  519. X    XDrawRectangle(disp_info[disp_num].disp, 
  520. X               disp_info[disp_num].win_id,
  521. X                   disp_info[disp_num].rubber_band_gc,
  522. X                   left, top, width, height);
  523. X    /*
  524. X     * place the text file at the current pointer location
  525. X     */
  526. X    if ((text_file_ptr = fopen(disp_info[disp_num].dialog_text_return,"r")) == 
  527. X    NULL)
  528. X    {
  529. X        printf("ERROR: Could not open text file %s.\n", 
  530. X            disp_info[disp_num].dialog_text_return);
  531. X    }
  532. X    else
  533. X    {
  534. X        set_paused_cursors();                     /*this may take a while*/
  535. X
  536. X    disp_info[disp_num].orig_x = left;
  537. X        disp_info[disp_num].char_height =
  538. X           (disp_info[disp_num].the_font_struct)->max_bounds.ascent +
  539. X               (disp_info[disp_num].the_font_struct)->max_bounds.descent;
  540. X
  541. X    while ((c = getc(text_file_ptr)) != EOF)
  542. X    {
  543. X        ungetc(c, text_file_ptr);
  544. X        fgets(current_line, 510, text_file_ptr);       /*get the line*/
  545. X            for (length=0; current_line[length] != '\n'; length++)
  546. X        ;
  547. X    
  548. X        /*draw this line to all the displays*/
  549. X            for (i=0; i< num_of_disps; i++)
  550. X            {
  551. X                if (disp_info[i].in_session_bool) /*if window is alive*/
  552. X                {
  553. X                XDrawString(disp_info[i].disp, disp_info[i].win_id,
  554. X                        disp_info[disp_num].win_gc[i], 
  555. X                    left, top, current_line, length);
  556. X                }
  557. X            }
  558. X            left = disp_info[disp_num].orig_x;
  559. X            top += disp_info[disp_num].char_height;
  560. X
  561. X        }
  562. X    fclose(text_file_ptr);
  563. X        for (i=0; i< num_of_disps; i++)
  564. X            if (disp_info[i].in_session_bool) /*if window is alive*/
  565. X                XFlush(disp_info[i].disp);
  566. X
  567. X        unset_paused_cursors();                   /*done with long action*/
  568. X        /*go back to appropriate drawing tool*/ 
  569. X        menu_selection(disp_num, 0, &num_people_drawing, 
  570. X               disp_info[disp_num].previous_scrawl_mode);
  571. X    }
  572. X}
  573. X
  574. X
  575. X/*
  576. X * place_an_image - this routine places an image at the current location
  577. X */
  578. Xplace_an_image(disp_num, the_event)
  579. Xint disp_num;
  580. XXButtonPressedEvent *the_event;
  581. X{
  582. X    int i, top, left, width, height, depth;
  583. X    XImage *the_image;
  584. X    char *mesg;
  585. X
  586. X    (num_people_drawing)--;         /*done drawing now*/
  587. X    /*
  588. X     * erase rubber band box
  589. X     */
  590. X    top = disp_info[disp_num].cur_pos.y - 
  591. X    disp_info[disp_num].rubber_band_height/2;
  592. X    left = disp_info[disp_num].cur_pos.x - 
  593. X                disp_info[disp_num].rubber_band_width/2;
  594. X    width = disp_info[disp_num].rubber_band_width;
  595. X    height = disp_info[disp_num].rubber_band_height;
  596. X    XDrawRectangle(disp_info[disp_num].disp, 
  597. X               disp_info[disp_num].win_id,
  598. X                   disp_info[disp_num].rubber_band_gc,
  599. X                   left, top, width, height);
  600. X
  601. X    set_paused_cursors();     /*this will take a while*/
  602. X
  603. X    for (i=0; i<num_of_disps; i++)
  604. X    {
  605. X    if (disp_info[i].in_session_bool)
  606. X    {
  607. X            if ((the_image = read_image_from_disk(disp_info[i].disp, 
  608. X               disp_info[i].win_id,
  609. X                       disp_info[disp_num].dialog_text_return, &width, 
  610. X               &height, &depth)) == NULL)
  611. X        {
  612. X        printf("WARNING: image not displayed on display %s.\n",
  613. X               disp_args[i]);
  614. X        XClearArea(disp_info[i].disp, disp_info[i].win_id, 
  615. X               left, top, width, height, False);
  616. X
  617. X        XSetLineAttributes(disp_info[i].disp, 
  618. X                               disp_info[i].rubber_band_gc, 3, LineSolid,
  619. X                   CapButt, JoinBevel);
  620. X        if ((width > 4) && (height > 4))
  621. X        {
  622. X                    XDrawRectangle(disp_info[i].disp, 
  623. X                           disp_info[i].win_id,
  624. X                               disp_info[i].rubber_band_gc,
  625. X                               left + 2, top + 2, width - 4, height - 4);
  626. X        }
  627. X        XSetLineAttributes(disp_info[i].disp, 
  628. X                               disp_info[i].rubber_band_gc, 0, LineSolid,
  629. X                   CapButt, JoinBevel);
  630. X
  631. X        mesg = "You are missing an image here.";
  632. X        XDrawString(disp_info[i].disp, disp_info[i].win_id,
  633. X                    disp_info[i].fg_menu_gc, 
  634. X                (left + (width/2) - 88), (top + (height/2) + 5),
  635. X                 mesg, strlen(mesg));
  636. X        }
  637. X        else if (DefaultDepth(disp_info[i].disp, 
  638. X                 DefaultScreen(disp_info[i].disp)) != depth)
  639. X        {
  640. X        printf("WARNING: image not displayed on display %s. ",
  641. X               disp_args[i]);
  642. X        printf("(Inconsistent depths.)\n");
  643. X            XDestroyImage(the_image);       /*free the image memory*/
  644. X        }
  645. X        else
  646. X        {
  647. X                XPutImage(disp_info[i].disp, disp_info[i].win_id,
  648. X              disp_info[disp_num].win_gc[i], the_image, 
  649. X              0, 0, left, top, width, height);
  650. X                XFlush(disp_info[i].disp);
  651. X            XDestroyImage(the_image);
  652. X        }
  653. X    }
  654. X    }
  655. X    unset_paused_cursors();     /*done*/
  656. X
  657. X    /*go back to appropriate drawing tool*/ 
  658. X    menu_selection(disp_num, 0, &num_people_drawing, 
  659. X               disp_info[disp_num].previous_scrawl_mode);
  660. X}
  661. X
  662. X
  663. X/*
  664. X * place_a_bitmap - this routine places a bitmap at the current location
  665. X */
  666. Xplace_a_bitmap(disp_num, the_event)
  667. Xint disp_num;
  668. XXButtonPressedEvent *the_event;
  669. X{
  670. X    int top, left, width, height;
  671. X
  672. X    (num_people_drawing)--;       /*stop this dude's talley*/
  673. X
  674. X    /*
  675. X     * erase rubber band box
  676. X     */
  677. X    top = disp_info[disp_num].cur_pos.y - 
  678. X    disp_info[disp_num].rubber_band_height/2;
  679. X    left = disp_info[disp_num].cur_pos.x - 
  680. X                disp_info[disp_num].rubber_band_width/2;
  681. X    width = disp_info[disp_num].rubber_band_width;
  682. X    height = disp_info[disp_num].rubber_band_height;
  683. X    XDrawRectangle(disp_info[disp_num].disp, 
  684. X               disp_info[disp_num].win_id,
  685. X                   disp_info[disp_num].rubber_band_gc,
  686. X                   left, top, width, height);
  687. X    read_in_and_place_bitmap(disp_num,
  688. X                      disp_info[disp_num].dialog_text_return, 
  689. X                      the_event->x - width/2, the_event->y - height/2);
  690. X}
  691. X
  692. X
  693. X/*
  694. X * update_select_area - this function updates the rubber-band box that this
  695. X *             user is selecting an area with.  This function has no effect
  696. X *             other than visual.
  697. X */
  698. Xupdate_select_area(disp_num)
  699. Xint disp_num;
  700. X{
  701. X    Window rr, cr;                 /* <-- Some strange ass variables, dude.*/
  702. X    unsigned int mskr;
  703. X    int temp, rxr, ryr, win_x, win_y;
  704. X    int start_x, start_y, last_x, last_y;
  705. X
  706. X    if (disp_info[disp_num].first_point_bool)
  707. X    { 
  708. X        XQueryPointer(disp_info[disp_num].disp, 
  709. X              disp_info[disp_num].win_id, &rr, &cr, &rxr, 
  710. X              &ryr, &win_x, &win_y,&mskr);
  711. X    disp_info[disp_num].select_start_pos.x = win_x;
  712. X    disp_info[disp_num].select_start_pos.y = win_y;
  713. X        disp_info[disp_num].cur_pos.x = win_x;
  714. X        disp_info[disp_num].cur_pos.y = win_y;
  715. X        disp_info[disp_num].first_point_bool = FALSE;
  716. X    } 
  717. X
  718. X    XQueryPointer(disp_info[disp_num].disp,
  719. X              disp_info[disp_num].win_id, &rr, &cr, &rxr,
  720. X          &ryr, &win_x, &win_y,&mskr);
  721. X    /*
  722. X     * if the user has changed the x and y position of the pointer, change
  723. X     * the rubberband box.
  724. X     */
  725. X    if ((win_x != disp_info[disp_num].cur_pos.x) ||
  726. X        (win_y != disp_info[disp_num].cur_pos.y))
  727. X    {
  728. X    start_x = disp_info[disp_num].select_start_pos.x;
  729. X    start_y = disp_info[disp_num].select_start_pos.y;
  730. X    last_x = disp_info[disp_num].cur_pos.x;
  731. X    last_y = disp_info[disp_num].cur_pos.y;
  732. X
  733. X        if (start_x > last_x)  /*switch so lesser coordinate is first*/
  734. X        {
  735. X            temp = start_x;
  736. X        start_x = last_x;
  737. X        last_x = temp;
  738. X    }
  739. X    if (start_y > last_y)  /*switch so lesser coordinate is first*/
  740. X    {
  741. X            temp = start_y;
  742. X        start_y = last_y;
  743. X        last_y = temp;
  744. X    }
  745. X
  746. X    /*erase old one*/
  747. X        XDrawRectangle(disp_info[disp_num].disp, disp_info[disp_num].win_id,
  748. X                   disp_info[disp_num].rubber_band_gc,
  749. X               start_x, start_y, last_x - start_x,  last_y - start_y);
  750. X
  751. X    disp_info[disp_num].cur_pos.x = win_x;
  752. X    disp_info[disp_num].cur_pos.y = win_y;
  753. X    start_x = disp_info[disp_num].select_start_pos.x;
  754. X    start_y = disp_info[disp_num].select_start_pos.y;
  755. X    last_x = disp_info[disp_num].cur_pos.x;
  756. X    last_y = disp_info[disp_num].cur_pos.y;
  757. X
  758. X        if (start_x > last_x)  /*switch so lesser coordinate is first*/
  759. X        {
  760. X            temp = start_x;
  761. X        start_x = last_x;
  762. X        last_x = temp;
  763. X    }
  764. X    if (start_y > last_y)  /*switch so lesser coordinate is first*/
  765. X    {
  766. X            temp = start_y;
  767. X        start_y = last_y;
  768. X        last_y = temp;
  769. X    }
  770. X
  771. X        /* draw the new one */
  772. X        XDrawRectangle(disp_info[disp_num].disp, disp_info[disp_num].win_id,
  773. X                   disp_info[disp_num].rubber_band_gc,
  774. X               start_x, start_y, last_x - start_x,  last_y - start_y);
  775. X    XFlush(disp_info[disp_num].disp);
  776. X    }
  777. X}
  778. X
  779. X
  780. X/* 
  781. X * update_menu_highlights - this function highlights the proper menu selection.
  782. X *             It does not have any affect except visual upon any operations.
  783. X *             However, it could entail switching between menus on the menu
  784. X *             bar if the user has moved the mouse too far off the menu.
  785. X *             It also is responsible for updating the "item_selected" field
  786. X *             of the disp_info structure.
  787. X */
  788. Xupdate_menu_highlights(disp_num)
  789. X{
  790. X    int new_item_num, num_items, menu_num;
  791. X    Window rr, cr;
  792. X    unsigned int mskr;
  793. X    int rxr, ryr, win_x, win_y, oldy, old_item_selected;
  794. X
  795. X
  796. X    menu_num = disp_info[disp_num].current_menu;
  797. X    old_item_selected = disp_info[disp_num].menu[menu_num].item_selected;
  798. X    num_items = disp_info[disp_num].menu[menu_num].num_items;
  799. X    XQueryPointer(disp_info[disp_num].disp, 
  800. X          disp_info[disp_num].menu[menu_num].win_id,
  801. X              &rr, &cr, &rxr, &ryr, &win_x, &win_y,&mskr);
  802. X    
  803. X    /*first figure out what item the pointer is pointing at*/
  804. X    for (new_item_num=0; new_item_num < num_items; new_item_num++)
  805. X    {
  806. X        if (win_y < (new_item_num * MENU_ITEM_HEIGHT)+ MENU_ITEM_HEIGHT)
  807. X        break;
  808. X    }
  809. X
  810. X    /*only draw highlight if not the menu title, and not already selected*/
  811. X    if ((new_item_num != 0) && (new_item_num != old_item_selected))
  812. X    {
  813. X    disp_info[disp_num].menu[menu_num].item_selected = new_item_num;
  814. X    /*erase old highlight*/
  815. X    oldy = (old_item_selected * MENU_ITEM_HEIGHT) + 4;
  816. X        XDrawRectangle(disp_info[disp_num].disp, 
  817. X           disp_info[disp_num].menu[menu_num].win_id, 
  818. X           disp_info[disp_num].bg_menu_gc, 2, oldy,
  819. X           MENU_ITEM_WIDTH - 4, MENU_ITEM_HEIGHT - 6);
  820. X
  821. X    /*draw new highlight*/
  822. X        XDrawRectangle(disp_info[disp_num].disp, 
  823. X           disp_info[disp_num].menu[menu_num].win_id, 
  824. X           disp_info[disp_num].hi_menu_gc, 2, 
  825. X           4 + (MENU_ITEM_HEIGHT * new_item_num),
  826. X           MENU_ITEM_WIDTH - 4, MENU_ITEM_HEIGHT - 6);
  827. X    }
  828. X    else if ((new_item_num == 0) && (new_item_num != old_item_selected))
  829. X    {
  830. X    disp_info[disp_num].menu[menu_num].item_selected = new_item_num;
  831. X    /*erase old highlight*/
  832. X    oldy = (old_item_selected * MENU_ITEM_HEIGHT) + 4;
  833. X        XDrawRectangle(disp_info[disp_num].disp, 
  834. X           disp_info[disp_num].menu[menu_num].win_id, 
  835. X           disp_info[disp_num].bg_menu_gc, 2, oldy,
  836. X           MENU_ITEM_WIDTH - 4, MENU_ITEM_HEIGHT - 6);
  837. X    }
  838. X    else if ((new_item_num == 0) && (new_item_num == old_item_selected))
  839. X    {
  840. X        /*
  841. X     * The following code decides if it is necessary to release a menu,
  842. X     * and if so, it releases the old menu and pulls down a new one.
  843. X     */
  844. X        if ((win_x < 0) && (menu_num != 0))   /*move to the left one menu*/
  845. X    {
  846. X            disp_info[disp_num].pointer_state = BETWEEN_MENUS;
  847. X        draw_menu(disp_num);          /*retract the old menu*/
  848. X
  849. X            disp_info[disp_num].current_menu = menu_num-1;
  850. X            disp_info[disp_num].pointer_state = IN_MENU;
  851. X        disp_info[disp_num].menu[menu_num].item_selected = 0;
  852. X        draw_menu(disp_num);          /*draw the menus with this new info*/
  853. X    }
  854. X        else if ((win_x>(MENU_ITEM_WIDTH+1)) && (menu_num!=(NUM_OF_MENUS-1))) 
  855. X    {   /*move to the right one menu*/
  856. X            disp_info[disp_num].pointer_state = BETWEEN_MENUS;
  857. X        draw_menu(disp_num);          /*retract the old menu*/
  858. X
  859. X            disp_info[disp_num].current_menu = menu_num + 1;
  860. X            disp_info[disp_num].pointer_state = IN_MENU;
  861. X        disp_info[disp_num].menu[menu_num].item_selected = 0;
  862. X        draw_menu(disp_num);          /*draw the menus with this new info*/
  863. X    }
  864. X    }
  865. X}
  866. X
  867. X
  868. X/*
  869. X * block_until_input - this function blocks using a "select" until someone
  870. X *             inputs on one of the windows involved with the "wscrawling" 
  871. X *             session.  this function returns nothing.  the rest of the 
  872. X *             program looks at the input queues to decide whether or not
  873. X *             to scrawl and on which windows.
  874. X */
  875. Xblock_until_input()
  876. X{
  877. X    int i, read_mask, top_con_num;
  878. X    struct timeval timeout;
  879. X
  880. X    timeout.tv_sec = 5;
  881. X    timeout.tv_usec = 0;
  882. X
  883. X    for (i = read_mask = top_con_num = 0; i<num_of_disps; i++)
  884. X    {
  885. X        if (disp_info[i].in_session_bool == TRUE)
  886. X    {
  887. X            if (XPending(disp_info[i].disp))
  888. X            return(1); /*an event is pending on live display; don't block*/
  889. X
  890. X            read_mask |= 1 << disp_info[i].connection_num;
  891. X            if (disp_info[i].connection_num > top_con_num)
  892. X            top_con_num = disp_info[i].connection_num;
  893. X    }
  894. X    }
  895. X
  896. X    select(top_con_num+1, &read_mask, (int *) 0, (int *) 0, &timeout);
  897. X    return(1);      /*either an event has occured, or a timeout*/
  898. X}
  899. X
  900. X
  901. X/*
  902. X * process_event - this function takes an event off the event queue and
  903. X *             calls the appropriate routine to handle this type of event.
  904. X */
  905. Xprocess_event(disp_num, num_people_drawing)
  906. Xint disp_num;
  907. Xint *num_people_drawing;
  908. X{
  909. X    XEvent the_event;
  910. X    
  911. X    if (XPending(disp_info[disp_num].disp))
  912. X    {
  913. X        XNextEvent(disp_info[disp_num].disp, &the_event);
  914. X        switch (the_event.type)
  915. X    {
  916. X        case ButtonPress:
  917. X        handle_ButtonPress_event(&the_event, disp_num, 
  918. X                     num_people_drawing);
  919. X        break;
  920. X        case ButtonRelease:
  921. X        handle_ButtonRelease_event(&the_event, disp_num, 
  922. X                       num_people_drawing);
  923. X        break;
  924. X        case KeyPress:
  925. X        handle_KeyPress_event(&the_event, disp_num);
  926. X        break;
  927. X        case Expose:
  928. X        handle_Expose_event(&the_event, disp_num);
  929. X        break;
  930. X        case ConfigureNotify:
  931. X        handle_ConfigureNotify_event(&the_event, disp_num);
  932. X        break;
  933. X        case ClientMessage:
  934. X        handle_ClientMessage_event(&the_event, disp_num);
  935. X        break;
  936. X        case SelectionNotify:
  937. X        handle_SelectionNotify_event(&the_event, disp_num);
  938. X        break;
  939. X        default:
  940. X        break;
  941. X    }
  942. X    }
  943. X}
  944. X
  945. X
  946. X/*
  947. X * handle_ButtonPress_event - this function handles a ButtonPress event in
  948. X *                   this particular scrawler's window.  It is probably a
  949. X *                   scrawler starting to draw, but it could be a menu selection
  950. X *                   so that is checked for also.
  951. X */
  952. Xhandle_ButtonPress_event(our_event, disp_num, num_people_drawing)
  953. XXEvent *our_event;
  954. Xint disp_num, *num_people_drawing;
  955. X{
  956. X    XButtonPressedEvent *the_event;
  957. X    int menu_num, i;
  958. X
  959. X    the_event = (XButtonPressedEvent *) our_event;
  960. X
  961. X    if (disp_info[disp_num].scrawl_mode == RESPONDING_TO_DIALOG)
  962. X    return(0);    /*not allowed to do anything else*/
  963. X
  964. X    if (the_event->button == Button1)
  965. X    {
  966. X    if (the_event->window == disp_info[disp_num].win_id)
  967. X    {
  968. X            if (NOTHING_DRAWN_YET == TRUE)   /*global variable*/
  969. X                NOTHING_DRAWN_YET = FALSE;
  970. X        
  971. X            disp_info[disp_num].first_point_bool = TRUE; 
  972. X
  973. X        switch(disp_info[disp_num].scrawl_mode)
  974. X        {
  975. X        case PLACING_A_BITMAP:
  976. X                place_a_bitmap(disp_num, the_event);
  977. X            disp_info[disp_num].just_placed_something_bool = TRUE;
  978. X            break;
  979. X        case PLACING_AN_IMAGE:
  980. X                place_an_image(disp_num, the_event);
  981. X            disp_info[disp_num].just_placed_something_bool = TRUE;
  982. X            break;
  983. X        case PLACING_A_TEXTFILE:
  984. X                place_a_textfile(disp_num, the_event);
  985. X            disp_info[disp_num].just_placed_something_bool = TRUE;
  986. X            break;
  987. X            case TYPING:
  988. X                    disp_info[disp_num].pointer_state = NOT_PRESSED;
  989. X            break;
  990. X        default:
  991. X                    disp_info[disp_num].pointer_state = PRESSED;
  992. X            break;
  993. X            }
  994. X
  995. X        if (disp_info[disp_num].scrawl_mode == ERASING)
  996. X        {
  997. X        if (disp_info[disp_num].pen_width - 4 > 0)
  998. X        {
  999. X                    XMoveResizeWindow(disp_info[disp_num].disp, 
  1000. X             disp_info[disp_num].eraser_win_id, 
  1001. X             the_event->x - disp_info[disp_num].pen_width/2, 
  1002. X             the_event->y - disp_info[disp_num].pen_width/2,
  1003. X             disp_info[disp_num].pen_width - 4,
  1004. X             disp_info[disp_num].pen_width - 4);
  1005. X        }
  1006. X        else
  1007. X        {
  1008. X                    XMoveResizeWindow(disp_info[disp_num].disp, 
  1009. X             disp_info[disp_num].eraser_win_id, 
  1010. X             the_event->x - disp_info[disp_num].pen_width/2, 
  1011. X             the_event->y - disp_info[disp_num].pen_width/2,
  1012. X             1, 1);
  1013. X        }
  1014. X                XMapWindow(disp_info[disp_num].disp, 
  1015. X               disp_info[disp_num].eraser_win_id);
  1016. X            }
  1017. X
  1018. X        if (disp_info[disp_num].cursor_on == TRUE) /*erase the cursor*/
  1019. X        {
  1020. X             XDrawLine(disp_info[disp_num].disp, 
  1021. X          disp_info[disp_num].win_id,
  1022. X          disp_info[disp_num].cursor_gc, 
  1023. X          disp_info[disp_num].cur_pos.x, 
  1024. X          disp_info[disp_num].cur_pos.y, 
  1025. X          disp_info[disp_num].cur_pos.x + 
  1026. X          disp_info[disp_num].prompt_width, 
  1027. X          disp_info[disp_num].cur_pos.y);
  1028. X            }
  1029. X
  1030. X        if (disp_info[disp_num].scrawl_mode != TYPING)
  1031. END_OF_FILE
  1032. if test 35046 -ne `wc -c <'wscrawl/xaa'`; then
  1033.     echo shar: \"'wscrawl/xaa'\" unpacked with wrong size!
  1034. fi
  1035. # end of 'wscrawl/xaa'
  1036. fi
  1037. echo shar: End of archive 4 \(of 5\).
  1038. cp /dev/null ark4isdone
  1039. MISSING=""
  1040. for I in 1 2 3 4 5 ; do
  1041.     if test ! -f ark${I}isdone ; then
  1042.     MISSING="${MISSING} ${I}"
  1043.     fi
  1044. done
  1045. if test "${MISSING}" = "" ; then
  1046.     echo You have unpacked all 5 archives.
  1047.     rm -f ark[1-9]isdone
  1048. else
  1049.     echo You still need to unpack the following archives:
  1050.     echo "        " ${MISSING}
  1051. fi
  1052. ##  End of shell archive.
  1053. exit 0
  1054. dan
  1055. ----------------------------------------------------
  1056. O'Reilly && Associates   argv@sun.com / argv@ora.com
  1057. Opinions expressed reflect those of the author only.
  1058.