home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C / Libraries / stdwin / Ports / x11 / dialog.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-04-03  |  14.5 KB  |  645 lines  |  [TEXT/????]

  1. /* X11 STDWIN -- Dialog boxes */
  2.  
  3. #include "x11.h"
  4. #include "llevent.h"
  5.  
  6. /* Forward static function declarations */
  7.  
  8. static int dialog _ARGS((char *, char *, int, int, int));
  9. static void addbutton _ARGS((int, char *, int, bool, int, int, int, int));
  10. static int dialogeventloop _ARGS((int));
  11. static int charcode _ARGS((XKeyEvent *));
  12. static bool addchar _ARGS((int));
  13.  
  14. /* The kinds of dialogs we know */
  15.  
  16. #define MESSAGEKIND    0
  17. #define ASKYNCKIND    1
  18. #define ASKSTRKIND    2
  19.  
  20. /* Display a message and wait until acknowledged */
  21.  
  22. void
  23. wmessage(prompt)
  24.     char *prompt;
  25. {
  26.     (void) dialog(prompt, (char*)NULL, 0, MESSAGEKIND, 0);
  27. }
  28.  
  29. /* Ask a yes/no question.
  30.    Return value: yes ==> 1, no ==> 0, cancel (^C) ==> -1.
  31.    Only the first non-blank character of the string typed is checked.
  32.    The 'def' parameter is returned when an empty string is typed. */
  33.  
  34. int
  35. waskync(prompt, def)
  36.     char *prompt;
  37.     int def;
  38. {
  39.     return dialog(prompt, (char*)NULL, 0, ASKYNCKIND, def);
  40. }
  41.  
  42. /* Ask for a string. */
  43.  
  44. bool
  45. waskstr(prompt, buf, len)
  46.     char *prompt;
  47.     char *buf;
  48.     int len;
  49. {
  50.     return dialog(prompt, buf, len, ASKSTRKIND, TRUE);
  51. }
  52.  
  53. /* Definition for the current dialog box */
  54.  
  55. static struct windata box;    /* Window descriptor for the dialog box */
  56. static unsigned long fg, bg;    /* Foreground, background pixel values */
  57. static GC b_gc;            /* Corresponding Graphics Context */
  58.  
  59. /* Definition for a command "button" or text item in a dialog box */
  60.  
  61. struct button {
  62.     char type;        /* Button, label or text */
  63.     char *text;        /* The button label */
  64.     int ret;        /* Value to return when chosen */
  65.     struct windata wd;    /* Window description */
  66.     tbool down;        /* Button is pressed */
  67.     tbool inside;        /* Mouse inside button */
  68.     tbool hilite;        /* Button is hilited */
  69.     tbool def;        /* Set if default button */
  70. };
  71.  
  72. /* Constants used in button definition 'type' field above */
  73.  
  74. #define LABEL    'l'
  75. #define TEXT    't'
  76. #define BUTTON    'b'
  77.  
  78. static int nbuttons;        /* number of buttons and items */
  79. static struct button *buttonlist; /* Definition list */
  80.  
  81. /* Constants guiding button dimensions */
  82.  
  83. static int charwidth, textheight;
  84. static int promptwidth;
  85. static int buttonwidth;
  86.  
  87. /* Forward static function declarations */
  88.  
  89. static bool buttonevent _ARGS((struct button *, XEvent *));
  90. static void drawbutton _ARGS((struct button *));
  91. static void hilitebutton _ARGS((struct button *, bool));
  92. static void gettext _ARGS((char *, int));
  93. static void killbuttons _ARGS((void));
  94.  
  95. /* Dialog routine.
  96.    Create the window and subwindows, then engage in interaction.
  97.    Return the proper return value for waskync or waskstr.
  98. */
  99.  
  100. /* Dimension of buttons:
  101.    vertically:
  102.        - 1 pixel white space around text,
  103.        - 1 pixel wide border,
  104.        - q pixel white space around border
  105.    horizontally:
  106.        - 1 space character around text
  107.        - 1 pixel wide border
  108.        - 1 pixel white space around border
  109.    The prompt and reply are placed as buttons but their
  110.    border is white.
  111.    For a message, the OK button is to the right of the text,
  112.    separated from the prompt by two extra space characters.
  113. */
  114.  
  115. static int
  116. dialog(prompt, buf, len, kind, def)
  117.     char *prompt;        /* Prompt string */
  118.     char *buf;        /* Reply buffer, or NULL if unused */
  119.     int len;        /* Size of reply buffer */
  120.     int kind;        /* Dialog kind */
  121.     int def;        /* Default return value */
  122. {
  123.     WINDOW *act;
  124.     XFontStruct *save_wf= _wf;
  125.     int ret;
  126.     int textlen= 60;
  127.     
  128.     _wf= _wmf; /* So we can use wtextwidth() etc. */
  129.     
  130.     /* Compute various useful dimensions */
  131.     charwidth= wcharwidth(' ');
  132.     textheight= wlineheight();
  133.     promptwidth= wtextwidth(prompt, -1);
  134.     if (kind == MESSAGEKIND)
  135.         buttonwidth= wtextwidth("OK", 2);
  136.     else
  137.         buttonwidth= wtextwidth("Cancel", 6);
  138.     
  139.     /* Compute the outer box dimensions */
  140.     switch (kind) {
  141.     case MESSAGEKIND:
  142.         box.width= promptwidth + buttonwidth + 6*charwidth + 8;
  143.         box.height= textheight + 6;
  144.         break;
  145.     case ASKYNCKIND:
  146.         box.width= promptwidth + 2*charwidth + 4;
  147.         CLIPMIN(box.width, 3*(buttonwidth + 2*charwidth + 4));
  148.         box.height= 2 * (textheight + 6);
  149.         break;
  150.     default:
  151.         _wdebug(0, "dialog: bad kind");
  152.         kind= ASKSTRKIND;
  153.         /* Fall through */
  154.     case ASKSTRKIND:
  155.         box.width= promptwidth + 2*charwidth + 4;
  156.         CLIPMAX(textlen, len);
  157.         CLIPMIN(box.width, textlen*charwidth);
  158.         CLIPMIN(box.width, 2*(buttonwidth + 2*charwidth + 4));
  159.         box.height= 3 * (textheight + 6);
  160.         break;
  161.     }
  162.     CLIPMAX(box.width, WidthOfScreen(_ws));
  163.     CLIPMAX(box.height, HeightOfScreen(_ws));
  164.     
  165.     /* Compute the box position:
  166.        above a window if possible,
  167.        on the screen if necessary. */
  168.     
  169.     /* XXX Default placement could use an option resource */
  170.     
  171.     act= _w_get_last_active();
  172.     if (act != NULL) {
  173.         Window child_dummy;
  174.         if (!XTranslateCoordinates(_wd, act->wi.wid,
  175.             RootWindowOfScreen(_ws),
  176. #ifdef CENTERED
  177.             (act->wi.width - box.width) / 2,
  178.             (act->wi.height - box.height) / 2,
  179. #else
  180.             0,
  181.             0,
  182. #endif
  183.             &box.x, &box.y, &child_dummy))
  184.             act= NULL; /* Couldn't do it -- center on screen */
  185.     }
  186.     if (act == NULL) {
  187.         /* No window to cover */
  188. #ifdef CENTERED
  189.         /* center the box in the screen */
  190.         box.x= (WidthOfScreen(_ws) - box.width) / 2;
  191.         box.y= (HeightOfScreen(_ws) - box.height) / 2;
  192. #else
  193.         /* use top left screen corner */
  194.         box.x= box.y = 2*IBORDER + 1;
  195.         /* well, 1 pixel from the corner, to fool twm */
  196. #endif
  197.     }
  198.     
  199.     /* Clip the box to the screen */
  200.     
  201.     CLIPMAX(box.x, WidthOfScreen(_ws) - box.width);
  202.     CLIPMAX(box.y, HeightOfScreen(_ws) - box.height);
  203.     CLIPMIN(box.x, 0);
  204.     CLIPMIN(box.y, 0);
  205.     _wdebug(1, "dialog box: x=%d y=%d w=%d h=%d",
  206.         box.x, box.y, box.width, box.height);
  207.     
  208.     /* Create the box window and its GC */
  209.     
  210.     fg = _wgetpixel("menuForeground", "MenuForeground", _w_fgcolor);
  211.     bg = _wgetpixel("menuBackground", "MenuBackground", _w_bgcolor);
  212.     box.border= 2*IBORDER;
  213.     (void) _wcreate(&box, RootWindowOfScreen(_ws), 0, FALSE, fg, bg);
  214.     _wsaveunder(&box, True);
  215.     XSelectInput(_wd, box.wid,
  216.         ExposureMask|KeyPressMask|StructureNotifyMask);
  217.     b_gc= _wgcreate(box.wid, _wf->fid, fg, bg);
  218.     XSetPlaneMask(_wd, b_gc, fg ^ bg);
  219.     
  220.     /* Keep window managers happy:
  221.        a name for WM's that insist on displaying a window title;
  222.        class hints;
  223.        WM hints;
  224.        size hints to avoid interactive window placement;
  225.        and "transient hints" to link it to an existing window. 
  226.        The latter two only if the dialog box belongs to a window. */
  227.     
  228.     /* XXX This code could be unified with similar code in windows.c */
  229.     
  230.     /* The name is taken from _wprogname, to make it clear what
  231.        application a dialog belongs to (especially if there is
  232.        no window for it yet). */
  233.     
  234.     XStoreName(_wd, box.wid, _wprogname);
  235.     
  236.     /* Set class hints */
  237.     {
  238.         XClassHint classhint;
  239.         classhint.res_name= _wprogname;
  240.         classhint.res_class= "StdwinDialog";
  241.         XSetClassHint(_wd, box.wid, &classhint);
  242.     }
  243.     
  244.     /* Set WM hints */
  245.     {
  246.         XWMHints wmhints;
  247.         wmhints.flags = InputHint | StateHint;
  248.         wmhints.input = 1;
  249.         wmhints.initial_state = NormalState;
  250.         XSetWMHints(_wd, box.wid, &wmhints);
  251.     }
  252.     
  253.     if (act != NULL) {
  254.         XSizeHints sizehints;
  255.         
  256.         /* Pretend the user specified the size and position,
  257.            in an attempt to avoid window manager interference */
  258.         sizehints.x= box.x - box.border;
  259.         sizehints.y= box.y - box.border;
  260.         sizehints.width= box.width;
  261.         sizehints.height= box.height;
  262.         sizehints.flags= USPosition|USSize;
  263.         XSetNormalHints(_wd, box.wid, &sizehints);
  264.         
  265.         XSetTransientForHint(_wd, box.wid, act->wo.wid);
  266.     }
  267.     
  268.     /* Create the prompt label */
  269.     
  270.     addbutton(LABEL, prompt, def, FALSE,
  271.         2,
  272.         2,
  273.         promptwidth + 2*charwidth,
  274.         textheight + 2);
  275.     
  276.     /* Create the command buttons and text field (for ASKSTRKIND).
  277.        Note that our x, y convention differs from XCreateWindow:
  278.        we don't include the border width. */
  279.     
  280.     switch (kind) {
  281.     case MESSAGEKIND:
  282.         addbutton(BUTTON, "OK", def, FALSE,
  283.             box.width - buttonwidth - 2*charwidth - 2, 2,
  284.             buttonwidth + 2*charwidth,
  285.             textheight + 2);
  286.         break;
  287.     case ASKYNCKIND:
  288.         addbutton(BUTTON, "Yes", 1, def==1,
  289.             2,
  290.             box.height - textheight - 4,
  291.             buttonwidth + 2*charwidth,
  292.             textheight + 2);
  293.         addbutton(BUTTON, "No", 0, def==0,
  294.             buttonwidth + 2*charwidth + 6,
  295.             box.height - textheight - 4,
  296.             buttonwidth + 2*charwidth,
  297.             textheight + 2);
  298.         addbutton(BUTTON, "Cancel", -1, def==-1,
  299.             box.width - buttonwidth - 2*charwidth - 2,
  300.             box.height - textheight - 4,
  301.             buttonwidth + 2*charwidth,
  302.             textheight + 2);
  303.         break;
  304.     case ASKSTRKIND:
  305.         addbutton(BUTTON, "OK", 1, FALSE,
  306.             2,
  307.             box.height - textheight - 4,
  308.             buttonwidth + 2*charwidth,
  309.             textheight + 2);
  310.         addbutton(BUTTON, "Cancel", 0, FALSE,
  311.             box.width - buttonwidth - 2*charwidth - 2,
  312.             box.height - textheight - 4,
  313.             buttonwidth + 2*charwidth,
  314.             textheight + 2);
  315.         addbutton(TEXT, buf, def, FALSE,
  316.             2,
  317.             textheight + 8,
  318.             box.width - 4,
  319.             textheight + 2);
  320.         break;
  321.     }
  322.     
  323.     /* Finish the work.
  324.        Map the window, process events, extract text,
  325.        destroy everything, return value. */
  326.     
  327.     XMapRaised(_wd, box.wid);
  328.     
  329.     ret=dialogeventloop(def);
  330.     if (kind == ASKSTRKIND && ret < 0)
  331.         ret= 0;
  332.     
  333.     if (ret > 0 && len > 0)
  334.         gettext(buf, len);
  335.     
  336.     XFreeGC(_wd, b_gc);
  337.     killbuttons();
  338.     XDestroyWindow(_wd, box.wid);
  339.     XFlush(_wd);
  340.     
  341.     _wf= save_wf;
  342.     
  343.     return ret;
  344. }
  345.  
  346. /* Add a command button */
  347.  
  348. static void
  349. addbutton(type, text, ret, def, x, y, width, height)
  350.     int type;
  351.     char *text;
  352.     int ret;
  353.     bool def;
  354.     int x, y, width, height;
  355. {
  356.     struct button b;
  357.     int cursor= (type == BUTTON) ? XC_arrow : 0;
  358.     
  359.     b.type= type;
  360.     b.text= strdup(text);
  361.     b.ret= ret;
  362.     b.def= def;
  363.     
  364.     b.wd.x= x;
  365.     b.wd.y= y;
  366.     b.wd.width= width;
  367.     b.wd.height= height;
  368.     
  369.     if (type == LABEL)
  370.         b.wd.border= 0;
  371.     else
  372.         b.wd.border= IBORDER;
  373.     (void) _wcreate(&b.wd, box.wid, cursor, TRUE, fg, bg);
  374.     XSelectInput(_wd, b.wd.wid,
  375.         type == BUTTON ?
  376.             ExposureMask|ButtonPressMask|ButtonReleaseMask|
  377.             EnterWindowMask|LeaveWindowMask
  378.         :    ExposureMask);
  379.     
  380.     b.down= 0;
  381.     b.inside= FALSE;
  382.     b.hilite= type == TEXT && text[0] != EOS;
  383.     
  384.     L_APPEND(nbuttons, buttonlist, struct button, b);
  385. }
  386.  
  387. /* Dialog event processing loop.
  388.    Return number of button selected, -1 for Return, -2 for ^C. */
  389.  
  390. static struct button *whichbutton(); /* Forward */
  391.  
  392. static int
  393. dialogeventloop(def)
  394.     int def;
  395. {
  396.     for (;;) {
  397.         XEvent e;
  398.         struct button *bp;
  399.         int c;
  400.         
  401.         XNextEvent(_wd, &e);
  402.         _wdebug(3, "dlog2: evt type %d", e.type);
  403.         bp= whichbutton(e.xbutton.window);
  404.         if (bp != NULL) {
  405.             if (buttonevent(bp, &e)) {
  406.                 return bp->ret;
  407.             }
  408.         }
  409.         else if (e.xbutton.window == box.wid || e.type == KeyPress) {
  410.             switch (e.type) {
  411.             
  412.             case KeyPress:
  413.                 switch (c= charcode(&e.xkey)) {
  414.                 case EOS:
  415.                     break;
  416.                 case '\003': /* ^C, interrupt */
  417.                     return -1;
  418.                     /* Fall through */
  419.                 case '\n':
  420.                 case '\r':
  421.                     return def;
  422.                 default:
  423.                     if (!addchar(c))
  424.                         XBell(_wd, 0);
  425.                     break;
  426.                 }
  427.                 break;
  428.             
  429.             case MapNotify:
  430.                 /* Could set the input focus if another
  431.                    of our windows has it */
  432.                 break;
  433.             
  434.             default:
  435.                 _wdebug(3, "dialog: box ev %d", e.type);
  436.                 if (e.type == ButtonPress)
  437.                     XBell(_wd, 0);
  438.                 break;
  439.             
  440.             }
  441.         }
  442.         else {
  443.             switch (e.type) {
  444.             case ButtonPress:
  445.             case KeyPress:
  446.                 _wdebug(3, "dialog: alien press %d", e.type);
  447.                 XBell(_wd, 0);
  448.                 break;
  449.             case ButtonRelease:
  450.             case MotionNotify:
  451.                 _wdebug(3, "dialog: alien move %d", e.type);
  452.                 break;
  453.             default:
  454.                 _wdebug(3, "dialog: alien llev %d", e.type);
  455.                 _w_ll_event(&e);
  456.                 break;
  457.             }
  458.         }
  459.     }
  460. }
  461.  
  462. /* Find out which button a given Window ID belongs to (if any) */
  463.  
  464. static struct button *
  465. whichbutton(w)
  466.     Window w;
  467. {
  468.     int i;
  469.     
  470.     for (i= nbuttons; --i >= 0; ) {
  471.         if (w == buttonlist[i].wd.wid)
  472.             return &buttonlist[i];
  473.     }
  474.     return NULL;
  475. }
  476.  
  477. /* Return the character code corresponding to a key event.
  478.    Returns EOS if no ASCII character. */
  479.  
  480. static int
  481. charcode(kep)
  482.     XKeyEvent *kep;
  483. {
  484.     KeySym keysym;
  485.     char strbuf[10];
  486.     
  487.     if (XLookupString(kep, strbuf, sizeof strbuf,
  488.         &keysym, (XComposeStatus*)NULL) <= 0)
  489.         return EOS;
  490.     else
  491.         return strbuf[0];
  492. }
  493.  
  494. /* Append a character to the text item.
  495.    Here it is that we assume that the text item is last.
  496.    Unfortunately we can't use textedit (yet) to support all
  497.    desirable functionality, but we do support backspace
  498.    and overwriting the entire text. */
  499.  
  500. static bool
  501. addchar(c)
  502.     int c;
  503. {
  504.     struct button *bp= &buttonlist[nbuttons - 1];
  505.     int i;
  506.     
  507.     if (bp->type != TEXT || bp->text == NULL)
  508.         return FALSE;
  509.     i= strlen(bp->text);
  510.     if (c == '\b' || c == '\177' /*DEL*/ ) {
  511.         if (i > 0) {
  512.             bp->text[i-1]= EOS;
  513.             bp->hilite= FALSE;
  514.         }
  515.     }
  516.     else {
  517.         if (bp->hilite) {
  518.             i= 0;
  519.             bp->hilite= FALSE;
  520.         }
  521.         bp->text= realloc(bp->text, (unsigned)(i+2));
  522.         if (bp->text != NULL) {
  523.             bp->text[i]= c;
  524.             bp->text[i+1]= EOS;
  525.         }
  526.     }
  527.     drawbutton(bp);
  528.     return TRUE;
  529. }
  530.  
  531. /* Process an event directed to a given button.
  532.    This also updates the highlighting. */
  533.  
  534. static bool
  535. buttonevent(bp, xep)
  536.     struct button *bp;
  537.     XEvent *xep;
  538. {
  539.     bool hit= FALSE;
  540.     
  541.     switch (xep->type) {
  542.     
  543.     case Expose:
  544.         drawbutton(bp);
  545.         return FALSE;
  546.     
  547.     case ButtonPress:
  548.         if (bp->down == 0)
  549.             bp->down= xep->xbutton.button;
  550.         break;
  551.     
  552.     case ButtonRelease:
  553.         if (bp->down == xep->xbutton.button) {
  554.             hit= bp->inside;
  555.             bp->down= 0;
  556.         }
  557.         break;
  558.     
  559.     case EnterNotify:
  560.         bp->inside= TRUE;
  561.         break;
  562.     
  563.     case LeaveNotify:
  564.         bp->inside= FALSE;
  565.         break;
  566.     
  567.     default:
  568.         return FALSE;
  569.     
  570.     }
  571.     hilitebutton(bp, bp->down > 0 && bp->inside);
  572.     return hit;
  573. }
  574.  
  575. /* Draw procedure to draw a command button or text item */
  576.  
  577. static void
  578. drawbutton(bp)
  579.     struct button *bp;
  580. {
  581.     char *text= bp->text == NULL ? "" : bp->text;
  582.     int len= strlen(text);
  583.     int width= XTextWidth(_wmf, text, len);
  584.     int x= (bp->type == BUTTON) ? (bp->wd.width - width) / 2 : charwidth;
  585.     int y= (bp->wd.height + _wmf->ascent - _wmf->descent) / 2;
  586.     
  587.     XClearWindow(_wd, bp->wd.wid);
  588.     XDrawString(_wd, bp->wd.wid, b_gc, x, y, text, len);
  589.     /* Indicate the default button with an underline */
  590.     if (bp->def) {
  591.         unsigned long ulpos, ulthick;
  592.         if (!XGetFontProperty(_wmf, XA_UNDERLINE_POSITION, &ulpos))
  593.             ulpos= _wmf->descent/2;
  594.         if (!XGetFontProperty(_wmf, XA_UNDERLINE_THICKNESS,&ulthick)) {
  595.             ulthick= _wmf->descent/3;
  596.             CLIPMIN(ulthick, 1);
  597.         }
  598.         _winvert(bp->wd.wid, b_gc,
  599.             x, (int)(y + ulpos), width, (int)ulthick);
  600.     }
  601.     if (bp->hilite)
  602.         _winvert(bp->wd.wid, b_gc,
  603.             0, 0, bp->wd.width, bp->wd.height);
  604. }
  605.  
  606. /* Highlight or unhighlight a command button */
  607.  
  608. static void
  609. hilitebutton(bp, hilite)
  610.     struct button *bp;
  611.     bool hilite;
  612. {
  613.     if (bp->hilite != hilite) {
  614.         _winvert(bp->wd.wid, b_gc,
  615.             0, 0, bp->wd.width, bp->wd.height);
  616.         bp->hilite= hilite;
  617.     }
  618. }
  619.  
  620. /* Extract the text from the text item */
  621.  
  622. static void
  623. gettext(buf, len)
  624.     char *buf;
  625.     int len;
  626. {
  627.     struct button *bp= &buttonlist[nbuttons - 1];
  628.     
  629.     if (bp->type != TEXT || bp->text == NULL)
  630.         return;
  631.     strncpy(buf, bp->text, len-1);
  632.     buf[len-1]= EOS;
  633. }
  634.  
  635. /* Destroy all buttons and associated data structures */
  636.  
  637. static void
  638. killbuttons()
  639. {
  640.     int i;
  641.     for (i= 0; i < nbuttons; ++i)
  642.         free(buttonlist[i].text);
  643.     L_SETSIZE(nbuttons, buttonlist, struct button, 0);
  644. }
  645.