home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / vol_300 / 328_02 / wpulldn.c < prev    next >
C/C++ Source or Header  |  1991-03-17  |  16KB  |  837 lines

  1. /* wpulldn.c -
  2.  *     source file for pulldown menus.
  3.  *
  4.  *
  5.  *     method - user calls wpulldown () passing addr of top menu
  6.  *        wpulldown - installs a keytrap routine (event_handler)
  7.  *                        - draws the menu choices on the top line
  8.  *              - 'shrinks' the fullscreen window down 2 lines.
  9.  *
  10.  *
  11.  *        event_handler()  scans every keystroke.
  12.  *              - detects if MOUSE on top line or is a menu key
  13.  *              - if currently doing menu,
  14.  *                - sets flag indicating need to chagne choices
  15.  *              - if not currently doing menu,
  16.  *                    - activates menu program.
  17.  *
  18.  *        menu_manager()
  19.  *              - reentrant program,
  20.  *                   calls itself for nested menus.
  21.  *              - backs out if need to change main menu choice.
  22.  *                        - displays menus and processes keystrokes.
  23.  *
  24.  *
  25.  *     NOTES:
  26.  *            1) see definition of WMENU below for menu structure.
  27.  *            2) user-driven re-entrant menu selections are avoided
  28.  *            by setting OFF mu_enable before calling functions,
  29.  *            and turning ON mu_enabel after exiting function.
  30.  *
  31.  * 
  32.  */
  33. #include "wsys.h"
  34.  
  35.  
  36. int wpulldown_enable =1;        /* global var. for disabling menu */
  37.  
  38. #include <string.h>
  39. #include <ctype.h>
  40.  
  41.  
  42. /* in large model, may need to normalize menu pointers.
  43.  */
  44. #undef     NLZ_MENU
  45.  
  46. #ifdef __LARGE__
  47.     #define NLZ_MENU(xx)  nlz_menu (xx)
  48. #else
  49. #ifdef __COMPACT__
  50.     #define NLZ_MENU(xx)  nlz_menu (xx)
  51. #endif        /* matches COMPACT */
  52. #endif        /* matches LARGE */
  53.  
  54.  
  55.  
  56. #ifdef         NLZ_MENU
  57.  
  58.     void nlz_menu (WMENU *m);
  59.  
  60.     void nlz_menu (WMENU *menu)
  61.         {
  62.         WMENU *item;
  63.  
  64.  
  65.         for ( item= menu; item->mu_entry !=NULL; ++item )
  66.             {
  67.  
  68.             if ( item->mu_menu )
  69.                 {
  70.                 /* normalize nested menus, too
  71.                  */
  72.                 _NORMALIZE (item->mu_menu);
  73.                 nlz_menu (item->mu_menu);
  74.                 }
  75.             _NORMALIZE (item->mu_entry);
  76.             }
  77.         return;    /* nlz_menu */
  78.         }
  79.  
  80. #endif    /* NLZ_MENU */
  81.  
  82.  
  83.  
  84.  
  85. #if 0
  86.     /* non-compiling inclusion of WMENU structure.
  87.      *  - included for conveinience
  88.      */
  89.  
  90.  
  91. struct WMENU_st
  92.     {
  93.     char         *mu_entry;      /*text for menu line */
  94.     void         (*mu_func)(void);
  95.     char         *mu_help;       /*help              */
  96.     struct WMENU_st     *mu_menu;       /*nested menu        */
  97.     unsigned char     *mu_enable;    /*0=disable this entry*/
  98.     int               mu_highlight;   /*which letter (0-n) to highlight */
  99.     int        mu_key;        /*keypress to select this one     */
  100.     };
  101.  
  102. typedef struct WMENU_st  WMENU;
  103.  
  104.  
  105.  
  106.  
  107. #endif /* non-compiling inclusion of WMENU structure. */
  108.  
  109.  
  110.  
  111. /* space alloted to each choice on top line
  112.  */
  113. #define MENU_SPACING 10
  114.  
  115. #define DOUBLE_LINE_CHAR 205
  116.  
  117.  
  118. /* save any other keytrap routines, so event_handler can chain to them
  119.  */
  120. static  int (*oldtrap) (int) = NULL;
  121.  
  122.  
  123. /* save ptr to top menu in nested structure
  124.  * save number of  last entry in topmenu.
  125.  */
  126. static  WMENU  *topmenu;
  127. static  int    W_NEAR max_topchoice = 0;
  128.  
  129.  
  130.  
  131.  
  132.  
  133. /* flags to control re=entrant calls
  134.  *    ( note that the keyboard trap is called whether in menu or out of it
  135.  *      value is -1 if not doing menu, 0... max_topchoice if doing menu.
  136.  */
  137. static int W_NEAR      in_menu    = -1;
  138. static int W_NEAR      savechoice = -1;
  139.  
  140.  
  141. /* flag to identify topmost hanging menu
  142.  */
  143. static char W_NEAR     nested = 0;
  144.  
  145.  
  146.  
  147.  
  148.  
  149. /* internal subroutines
  150.  */
  151.  
  152.     /* function to examine each keystroke
  153.      * call menu if indicated
  154.      */
  155. static int event_handler (int);
  156.  
  157.     /* menu function
  158.      */
  159. static int W_NEAR  execute_choice ( WMENU *menu );
  160.  
  161.     /* draw top line of screen
  162.      */
  163. static void     W_NEAR draw_topline (int);
  164.  
  165.     /* draw pulldown menus
  166.      */
  167. static void W_NEAR draw_menu    (WMENU *m, int n);
  168. static void W_NEAR draw_item  
  169.              (WMENU *menu, int m, unsigned char ch, unsigned char ch2);
  170.  
  171.  
  172.     /* locate new choices
  173.      */
  174. static int  W_NEAR find_choice (int key);
  175. static void W_NEAR rotate  ( WMENU *menu, int *current, int max, int dir );
  176.  
  177.  
  178.     /* check out next menu table prior to setting up it's window
  179.      *         returns: offset of first active choice in menu. (-1 if none).
  180.      *        alters:  size of menu box to draw, rows and cols.
  181.      */
  182. static int W_NEAR any_active ( WMENU *menu, int *xmax, int *ymax );
  183.  
  184.  
  185.  
  186.  
  187.  
  188. /* installation routine
  189.  */
  190. void wpulldown ( WMENU *user_menu )
  191.     {
  192.  
  193.     oldtrap = wkeytrap;
  194.     wkeytrap = event_handler;
  195.  
  196.  
  197.  
  198.     topmenu = user_menu;
  199.  
  200.  
  201.  
  202.     /* in large and comapct models,
  203.      * make sure menus are normaliized
  204.      */
  205.  
  206.     #ifdef NLZ_MENU
  207.  
  208.         _NORMALIZE (topmenu);
  209.  
  210.         NLZ_MENU (topmenu);
  211.  
  212.     #endif /* NLZ_MENU */
  213.  
  214.  
  215.  
  216.  
  217.  
  218.  
  219.     /* count items in the top menu, determine max # of items.
  220.      * note that max_topchoice = Last valid subscript number.
  221.      */
  222.     for (     max_topchoice = 0; 
  223.             (topmenu+max_topchoice) ->mu_entry != NULL;
  224.             )
  225.         {
  226.         ++max_topchoice;    /* not incremented after final line */
  227.         }
  228.  
  229.     /* draw top line onscreen with no choice
  230.      */
  231.     wpulldown_draw ();
  232.  
  233.  
  234.  
  235.     /* shrink the fullscreen window to avoid stepping on the 2 lines
  236.      */
  237.     wfullscreen-> wintop   =2;
  238.     wfullscreen-> winymax -=2;
  239.  
  240.     wfullscreen-> winy = min ( wfullscreen->winy,  wfullscreen->winymax);
  241.  
  242.  
  243.  
  244.     return ;    /* wpulldown */
  245.     }
  246.  
  247.  
  248.  
  249.  
  250. static int event_handler (int key)
  251.     {
  252.     int     choice;
  253.     int     menu_change;
  254.     char     *save_help;
  255.  
  256.  
  257.     if  (oldtrap)
  258.         {
  259.         key = (*oldtrap) (key);
  260.         }
  261.  
  262.     if ( savechoice >= 0 )
  263.         {
  264.         /* trying to back out of menu/function
  265.          * by continually providing ESCAPEs to the
  266.          * function that called wgetc()
  267.          *
  268.          * so setup one extra ESCAPE and return ESCAPE
  269.          */
  270.         wungetc (ESCAPE);
  271.         return  (ESCAPE);
  272.         }
  273.  
  274.  
  275.     
  276.     choice = wpulldown_enable ? find_choice (key) : -1;
  277.  
  278.  
  279.  
  280.     while ( choice >= 0 )
  281.         {
  282.         if ( in_menu >= 0 )
  283.             {
  284.             /* need to back out
  285.              * of menu or any routines menu may have called.
  286.              *
  287.              * setup to feed repeated ESCAPES to routines.
  288.              *
  289.              */
  290.             savechoice = choice;    /* save for reactivation  */
  291.  
  292.             choice  = -1;
  293.  
  294.             wungetc (ESCAPE);
  295.             return  (ESCAPE);
  296.             }
  297.         else
  298.             {
  299.             /* not a re-entrant call - first time in menu.
  300.              *
  301.              * setup top line indicating menu choice,
  302.              * setup position for pulldown menu
  303.              */
  304.             save_help = whelp_ptr;
  305.  
  306.             /* draw top line onscreen with no choice
  307.              */
  308.             wopen ( 0,0, wxabsmax+1, 2,
  309.                 wmenuattr, NO_BORDER, 0, 0 );
  310.  
  311.             w0-> winputstyle &= ~(WPUTWRAP+WPUTSCROLL);
  312.  
  313.             draw_topline (choice);
  314.  
  315.             wgoto ( choice * MENU_SPACING, 0 );
  316.  
  317.             in_menu = choice;
  318.             nested  = 0;
  319.             menu_change = execute_choice (& topmenu[choice] );
  320.             in_menu = -1;
  321.  
  322.  
  323.             wabandon ();
  324.  
  325.             /* if another choice was made while executing menu,
  326.              * retrieve that choice
  327.              */
  328.             if ( savechoice >= 0 )
  329.                 {
  330.                 choice  = savechoice;    /* do next choice */
  331.                 savechoice = -1;
  332.  
  333.                 /* one extra ESCAPE was placed in the
  334.                  * unget buffer
  335.                  */
  336.                 wgetc ();
  337.  
  338.                 }
  339.             else
  340.             if ( menu_change == LEFT_ARROW )
  341.                 {
  342.                 rotate (topmenu, &choice, max_topchoice, -1);
  343.                 }
  344.             else
  345.             if ( menu_change == RIGHT_ARROW )
  346.                 {
  347.                 rotate (topmenu, &choice, max_topchoice, +1);
  348.                 }
  349.             else
  350.                 {
  351.                 choice = -1;
  352.                 }
  353.  
  354.             whelp_ptr = save_help;
  355.  
  356.             }
  357.  
  358.  
  359.         /* key that invoked menu should not be passed back to caller.
  360.          */
  361.         key = 0;
  362.  
  363.         /* restore the topline to show no 'selected' choice
  364.          */
  365.         wpulldown_draw ();
  366.  
  367.  
  368.         }
  369.  
  370.  
  371.  
  372.     return (key);                     /* event_handler */
  373.     }
  374.  
  375.  
  376.  
  377.  
  378. static int W_NEAR execute_choice ( WMENU *menu )
  379.     {
  380.     WMENU *item;
  381.  
  382.     int   left, top, xmax, ymax;        /* window co-ords */
  383.  
  384.     int   choice = -1;
  385.     int   newchoice;
  386.  
  387.     int key;
  388.  
  389.     int return_key = ESCAPE;
  390.  
  391.     unsigned char brightattr;
  392.  
  393.     int     box_type;
  394.  
  395.  
  396.     box_type =  ( nested ) ?  SINGLE_BORDER : HANGING_BORDER;
  397.     nested   =  1;
  398.  
  399.     /* setup help ptr
  400.      */
  401.     whelp_ptr = menu-> mu_help;
  402.  
  403.     if ( menu->mu_func )
  404.         {
  405.         /* execute function specified if table, if present
  406.          */
  407.         (* (menu->mu_func) )();
  408.         return(ESCAPE);        /* quit execute_choice */
  409.         }
  410.  
  411.     /* pick up ptr to nested menu
  412.      */
  413.     menu = menu-> mu_menu;
  414.  
  415.  
  416.     /* make sure at least one choice is active.
  417.      *         Also, counts entries and length of entries in nested menu
  418.      */
  419.     if (  (choice = any_active ( menu, &xmax, &ymax ))  < 0 )
  420.         {
  421.         /* no entries were active
  422.          */
  423.         return ( wgetc() );        /* get next keystroke to move off this item */
  424.         }
  425.  
  426.     /* setup position (1 to left, 1 down
  427.      * open window, draw choices
  428.      */
  429.     wsetlocation ( WLOC_ATCUR, 1, 1 );
  430.  
  431.     wlocate ( &left, &top, xmax, ymax );
  432.     wopen   ( left,  top, xmax, ymax, wmenuattr,
  433.             box_type, wmenuattr, WSAVE2RAM );
  434.  
  435.     brightattr = wmenuattr | BRIGHT;
  436.  
  437.  
  438.     draw_menu ( menu, choice );
  439.  
  440.  
  441.     do     {
  442.         newchoice = choice;
  443.  
  444.         whelp_ptr = menu[choice].mu_help;
  445.  
  446.         key = wgetc ();
  447.  
  448.  
  449.         if ( isascii (key) && isprint (key) )
  450.             {
  451.             /* match printable keystroke to menu entry
  452.              */
  453.             key = toupper (key);
  454.  
  455.             for ( newchoice = 0, item = menu;
  456.                   item->mu_entry !=NULL  && key != item->mu_key;
  457.                   ++item, ++newchoice )
  458.                   { /* empty */ }
  459.                   
  460.             if ( key != item->mu_key )
  461.                 {
  462.                 /* not found in above loop
  463.                  */
  464.                 newchoice = choice;
  465.                 }
  466.             else
  467.                 {
  468.                 /* choice was found, set up to execute it
  469.                  */
  470.                 key    = ENTER;
  471.                 }
  472.             }
  473.         else
  474.         if ( key == MOUSE  && wmouse.wms_inwindow )
  475.             {
  476.             if ( *  ( (menu+(wmouse.wms_y))->mu_enable) )
  477.                 {
  478.                 newchoice = wmouse.wms_y;
  479.  
  480.                 if ( wmouse.wms_used & WMS_LEFT_RLS )
  481.                     {
  482.                     /* menu item was selected
  483.                      */
  484.                     key = ENTER;
  485.                     }
  486.  
  487.                 }
  488.  
  489.             }
  490.         else
  491.         if ( key == UP_ARROW )
  492.             {
  493.             rotate (menu, &newchoice, ymax, -1);
  494.             }
  495.  
  496.                 else
  497.         if ( key == DN_ARROW )
  498.             {
  499.             rotate (menu, &newchoice, ymax, +1);
  500.             }
  501.         else
  502.         if ( key == LEFT_ARROW || key == RIGHT_ARROW )
  503.             {
  504.             return_key = key;    /* pass back to caller */
  505.             key = ESCAPE;
  506.             }
  507.  
  508.  
  509.         /* now, if necessary, redraw the menu to reflect changes
  510.          */
  511.         if ( choice != newchoice )
  512.             {
  513.             draw_item ( menu,   choice, wmenuattr,  brightattr );
  514.             draw_item ( menu,newchoice, wbuttonattr,0 );
  515.             choice = newchoice;
  516.             }
  517.  
  518.  
  519.         /* execute if ENTER pressed, (or via mouse, or via letter)
  520.          */
  521.         if ( key == ENTER )
  522.             {
  523.             wgoto ( 0, choice );
  524.  
  525.             wsetattr (0x07);
  526.  
  527.             return_key = execute_choice ( & menu[choice] );
  528.  
  529.             key = ESCAPE;
  530.             }
  531.  
  532.  
  533.  
  534.         }
  535.     while   ( key != ESCAPE );
  536.  
  537.  
  538.     wclose ();
  539.  
  540.  
  541.     return (return_key);        /* execute_choice */
  542.     }
  543.  
  544.  
  545.  
  546.  
  547.  
  548.  
  549.  
  550.  
  551.  
  552. static void W_NEAR draw_topline ( int choice )
  553.     {
  554.     WMENU     *item;
  555.     int     n_item;
  556.     int     x;
  557.     int     offset;
  558.  
  559.  
  560.     wgoto (0,0);
  561.     wclearline();
  562.  
  563.     for (     n_item=0, item= topmenu, x =0;
  564.         (item->mu_entry) != NULL;
  565.         ++item, ++n_item, x += MENU_SPACING )
  566.         {
  567.         wgoto ( x, 0 );
  568.  
  569.         if ( n_item == choice )
  570.             {
  571.             wsetattr (wbuttonattr);
  572.             }
  573.         else    {
  574.             wsetattr (wmenuattr);
  575.             }
  576.         wputs ( item->mu_entry );
  577.  
  578.         /* overwrite the highlighted letter
  579.          */
  580.         if ( *(item->mu_enable) && n_item != choice )
  581.             {
  582.             offset = item->mu_highlight;
  583.             wgoto ( x+ offset, 0 );
  584.             wbright();
  585.             wputc ( (item->mu_entry)[offset] );
  586.             wdim();
  587.             }
  588.  
  589.  
  590.         }
  591.  
  592.     /* second line
  593.      */
  594.     wgoto ( 0,1 );
  595.     wsetattr (wmenuattr);
  596.     for ( x = 0; x <= wxabsmax; ++x )
  597.         {
  598.         wputc (DOUBLE_LINE_CHAR);
  599.         }
  600.     return;     /* draw_topline */
  601.     }
  602.  
  603.  
  604. /* draw top line onscreen with no choice indicated
  605.  */
  606. void wpulldown_draw ( void )
  607.     {
  608.     wopen ( 0,0, wxabsmax+1, 2, wmenuattr, NO_BORDER, 0, 0 );
  609.  
  610.     w0-> winputstyle &= ~(WPUTWRAP+WPUTSCROLL);
  611.  
  612.     draw_topline (-1);
  613.  
  614.     wabandon ();
  615.  
  616.     return;        /* wpulldown_draw */
  617.     }
  618.  
  619.  
  620.  
  621. static void W_NEAR draw_menu ( WMENU *menu, int choice )
  622.     {
  623.     int n;
  624.     WMENU *item;
  625.  
  626.     unsigned char   brightattr;
  627.  
  628.     int x;
  629.  
  630.  
  631.     brightattr = wmenuattr | BRIGHT;
  632.  
  633.     wsetattr (wmenuattr);
  634.     wclear();
  635.  
  636.  
  637.     for (      n= 0, item =menu; item-> mu_entry !=NULL ; ++n, ++item      )
  638.         {
  639.  
  640.         wgoto ( 0,n );
  641.         if ( n == choice )
  642.             {
  643.             wsetattr ( wbuttonattr );
  644.             wputs (item->mu_entry);
  645.             wclearline ();
  646.             }
  647.         else
  648.             {
  649.  
  650.             wsetattr (wmenuattr);
  651.             wputs (item->mu_entry);
  652.             if ( (item->mu_enable)!=NULL  && *(item->mu_enable)!= 0 )
  653.                 {
  654.                 x = item->mu_highlight;
  655.                 wgoto (x, n );
  656.                 wsetattr (brightattr);
  657.                 wputc (item->mu_entry [x]);
  658.                 }
  659.             }
  660.         }
  661.  
  662.     return;    /* draw_menu */
  663.     }
  664.  
  665.  
  666. static void W_NEAR  draw_item (
  667.         WMENU *menu,  int choice,
  668.         unsigned char line_attr,
  669.         unsigned char ltr_attr   )
  670.     {
  671.  
  672.     int x;
  673.     WMENU *item;
  674.  
  675.  
  676.     item = menu+choice;
  677.     x    = item->mu_highlight;
  678.  
  679.  
  680.  
  681.  
  682.     wgoto ( 0, choice );
  683.     wsetattr (line_attr);
  684.     wclearline();
  685.     wputs (item-> mu_entry);
  686.  
  687.  
  688.     if ( ltr_attr )
  689.         {
  690.         wgoto (x, choice);
  691.         wsetattr ( ltr_attr );
  692.         wputc (item-> mu_entry[x] );
  693.         }
  694.  
  695.     return;    /* draw_item */
  696.     }
  697.  
  698.  
  699.  
  700.  
  701. /* look for user choices in the top menu line
  702.  * allow either a mouse LEFT-click in the top line
  703.  * or any ALT-key or FKEY that's in the top menu
  704.  *
  705.  * NOTE - this routine picks up any mouse activity on the top line.
  706.  *      uses in_menu to prevent rapid cycling in and out of same menu.
  707.  */
  708. static int W_NEAR   find_choice (int key)
  709.     {
  710.     int choice;
  711.     WMENU *item;
  712.     
  713.     int  scratch;
  714.  
  715.     if ( any_active( topmenu, &scratch, &scratch ) < 0 )     return -1;
  716.  
  717.     choice = -1;    /* default = not a choice */
  718.  
  719.  
  720.     if ( key == MOUSE  && wmouse.wms_yabs == 0 )
  721.         {
  722.         choice = wmouse.wms_xabs / MENU_SPACING;
  723.         choice = min ( max_topchoice, choice );
  724.  
  725.  
  726.         item = topmenu+choice;
  727.         if (  (item-> mu_enable)==NULL || *(item-> mu_enable)==0 )
  728.             {
  729.             choice = -1;        /* inactive */
  730.             }
  731.         if ( choice == in_menu )
  732.             {
  733.             /* this is just minor mouse mvt on the active choice.
  734.              * don't force ESCAPEs for the menu just to re-enter
  735.              * same one.
  736.              */
  737.             choice = -1;
  738.             }
  739.         }
  740.     else
  741.     if ( isALT (key)  ||  ( FKEY(1)<= key && key <= FKEY(10) ) )
  742.         {
  743.         /* search menu to see if it's a hot key
  744.          */
  745.         for (     item =topmenu; item->mu_entry  != NULL; ++item )
  746.             {
  747.             ++choice;       /* starts at -1, see above  */
  748.             if ( key == item-> mu_key )
  749.                 {
  750.                 if ( (item->mu_enable)==NULL ||  *(item->mu_enable)==0 )
  751.                     {
  752.                     choice = -1;
  753.                     }
  754.                 break;        /* quit for() loop */
  755.                 }
  756.             }
  757.         /* check results of loop - see if terminated without finding
  758.          */
  759.         if ( (key != item-> mu_key) )
  760.             {
  761.             choice = -1;
  762.             }
  763.         }
  764.  
  765.     return (choice);    /* find_choice */
  766.  
  767.     }
  768.  
  769.  
  770.  
  771. /* rotate() - function to move menu ptr up or down
  772.  *         bypasses inactive choices
  773.  */
  774. static void W_NEAR  rotate ( WMENU *menu, int *current,  int max, int dir )
  775.     {
  776.     int   n;
  777.     char  *ptr;
  778.  
  779.     int   done =0;
  780.  
  781.     n          = *current;
  782.  
  783.     while (  ! done  )
  784.         {
  785.         n += dir;
  786.  
  787.         if ( n < 0 )
  788.             {
  789.             n = max;
  790.             }
  791.         if ( n > max )
  792.             {
  793.             n = 0;
  794.             }
  795.         /* test to see if enable ptr is non-NULL and not pointing to 0 */
  796.         if ( NULL != (ptr=( (menu +n )->mu_enable )) )
  797.             {
  798.             done = *ptr;
  799.             } 
  800.  
  801.  
  802.         }
  803.  
  804.     *current = n;
  805.  
  806.     return;    /* rotate */
  807.     }
  808.  
  809.  
  810. static int W_NEAR any_active ( WMENU *menu, int *xmax, int *ymax )
  811.     {
  812.     int first_active = -1;
  813.     int x=0, y=0;
  814.     WMENU *item;
  815.     char  *text_ptr;
  816.     
  817.         
  818.     for ( item = menu; NULL != (text_ptr =(item-> mu_entry)); ++item )
  819.         {
  820.         if     (     ( first_active < 0        )
  821.             &&  ( item->mu_enable != NULL ) 
  822.             &&  ( *(item-> mu_enable) != 0) 
  823.             )
  824.             {
  825.             first_active =y;        /* finds first active line. */
  826.             }
  827.         ++y;
  828.         x = max ( x,  strlen ( text_ptr ) );    
  829.         }
  830.     
  831.     *xmax = x+1;
  832.     *ymax = y;
  833.     
  834.     return  first_active;    /* end of any_active() */
  835.     }    
  836.     
  837. /*------------------- end of WPULLDN.C -------------------*/