home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / x / volume10 / xt-examples / part05 / Menu.c < prev    next >
C/C++ Source or Header  |  1990-11-04  |  18KB  |  642 lines

  1. /***********************************************************
  2. Copyright 1990 by Digital Equipment Corporation, Maynard, Massachusetts.
  3.  
  4.                         All Rights Reserved
  5.  
  6. Permission to use, copy, modify, and distribute these examples for any
  7. purpose and without fee is hereby granted, provided that the above
  8. copyright notice appear in all copies and that both that copyright
  9. notice and this permission notice appear in supporting documentation,
  10. and that the name of Digital not be used in advertising or publicity
  11. pertaining to distribution of the software without specific, written
  12. prior permission.
  13.  
  14. DIGITAL AND THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
  15. SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  16. FITNESS, IN NO EVENT SHALL DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT
  17. OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
  18. OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  19. OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
  20. OR PERFORMANCE OF THIS SOFTWARE.
  21.  
  22. ******************************************************************/
  23.  
  24. #include <X11/IntrinsicP.h>    /* Intrinsics header file */
  25. #include <X11/StringDefs.h>    /* Resource string definitions */
  26. #include "MenuP.h"        /* Menu private header file */
  27. #include "LabelGadge.h"        /* To check LabelGadgets */
  28.  
  29. static Cardinal InsertBefore();
  30.  
  31. static XtResource resources[] = {
  32.     {XtNinsertPosition, XtCInsertPosition,
  33.     XtRFunction, sizeof(XtOrderProc),
  34.         XtOffsetOf(MenuRec, composite.insert_position),
  35.     XtRImmediate, (XtPointer) InsertBefore}
  36. };
  37.  
  38. static Cardinal InsertBefore(w)
  39.     Widget w;
  40. {
  41.     MenuWidget menu = (MenuWidget) XtParent(w);
  42.     MenuConstraint mc = (MenuConstraint) w->core.constraints;
  43.     int i;
  44.  
  45.     if (mc->menu.insert_before == NULL) {
  46.     return menu->composite.num_children;
  47.     }
  48.  
  49.     for (i = 0; i < menu->composite.num_children; i++) {
  50.     if (mc->menu.insert_before == menu->composite.children[i]) {
  51.         return i;
  52.     }
  53.     }
  54.  
  55.     return menu->composite.num_children;
  56. }
  57.  
  58. static XtResource constraintResources[] = {
  59.     {XtNinsertBefore, XtCInsertBefore, XtRWidget, sizeof(Widget),
  60.     XtOffsetOf(MenuConstraintRec, menu.insert_before), 
  61.     XtRImmediate, NULL},
  62. };
  63.  
  64. /* Forward declarations */
  65.  
  66. static void ClassInitialize(), ChangeManaged(), Initialize(),
  67.     ConstraintInitialize(), Resize(), InsertChild(),
  68.     Redisplay(), Destroy(), ConstraintGetValuesHook();
  69. static XtGeometryResult GeometryManager();
  70. static Boolean ConstraintSetValues();
  71.  
  72. static CompositeClassExtensionRec compositeExtension = {
  73.     /* next_extension        */ NULL,
  74.     /* record_type        */ NULLQUARK,
  75.     /* version            */ XtCompositeExtensionVersion,
  76.     /* record_size        */ sizeof(CompositeClassExtensionRec),
  77.     /* accepts_objects        */ TRUE
  78. };
  79.  
  80. static ConstraintClassExtensionRec constraintExtension = {
  81.     /* next_extension        */ NULL,
  82.     /* record_type        */ NULLQUARK,
  83.     /* version            */ XtConstraintExtensionVersion,
  84.     /* record_size        */ sizeof(ConstraintClassExtensionRec),
  85.     /* get_values_hook        */ ConstraintGetValuesHook
  86. };
  87.  
  88. MenuClassRec menuClassRec = {
  89.     /* Core class part */
  90.   {
  91.     /* superclass         */    (WidgetClass) &constraintClassRec,
  92.     /* class_name         */ "Menu",
  93.     /* widget_size         */ sizeof(MenuRec),
  94.     /* class_initialize      */ ClassInitialize,
  95.     /* class_part_initialize */ NULL,
  96.     /* class_inited          */    FALSE,
  97.     /* initialize         */    Initialize,
  98.     /* initialize_hook       */    NULL,
  99.     /* realize             */    XtInheritRealize,
  100.     /* actions             */    NULL,
  101.     /* num_actions         */    0,
  102.     /* resources         */    resources,
  103.     /* num_resources         */    XtNumber(resources),
  104.     /* xrm_class         */    NULLQUARK,
  105.     /* compress_motion         */    TRUE,
  106.     /* compress_exposure     */    XtExposeCompressMultiple,
  107.     /* compress_enterleave   */    TRUE,
  108.     /* visible_interest         */    FALSE,
  109.     /* destroy             */    Destroy,
  110.     /* resize             */    Resize,
  111.     /* expose             */    Redisplay,
  112.     /* set_values         */    NULL,
  113.     /* set_values_hook       */    NULL,            
  114.     /* set_values_almost     */    XtInheritSetValuesAlmost,  
  115.     /* get_values_hook       */    NULL,            
  116.     /* accept_focus         */    NULL,
  117.     /* version             */    XtVersion,
  118.     /* callback offsets      */    NULL,
  119.     /* tm_table              */    NULL,
  120.     /* query_geometry         */    XtInheritQueryGeometry,
  121.     /* display_accelerator   */    NULL,
  122.     /* extension         */    NULL,
  123.   },
  124.    /* Composite class part */
  125.   {
  126.     /* geometry_manager         */    GeometryManager,
  127.     /* change_managed         */    ChangeManaged,
  128.     /* insert_child         */    InsertChild,
  129.     /* delete_child         */    XtInheritDeleteChild,
  130.     /* extension         */    (XtPointer) &compositeExtension,
  131.   },
  132.    /* Constraint class part */
  133.   {
  134.    /* resources             */ constraintResources,
  135.    /* num_resources         */ XtNumber(constraintResources),
  136.    /* constraint_size         */ sizeof(MenuConstraintRec),
  137.    /* initialize         */ ConstraintInitialize,
  138.    /* destroy             */ NULL,
  139.    /* set_values         */ ConstraintSetValues,
  140.    /* extension             */ (XtPointer) &constraintExtension,
  141.   },
  142.    /* Menu class part */
  143.   {
  144.     /* extension         */    NULL,
  145.   }
  146. };
  147.  
  148. WidgetClass menuWidgetClass = (WidgetClass) &menuClassRec;
  149.  
  150. static void InsertChild(w)
  151.     Widget w;
  152. {
  153.     String params[2];
  154.     Cardinal num_params;
  155.     Widget parent = XtParent(w);
  156.  
  157.     if (!XtIsWidget(w) && !XtIsSubclass(w, labelGadgetClass)) {
  158.     params[0] = XtClass(w)->core_class.class_name;
  159.     params[1] = XtClass(parent)->core_class.class_name;
  160.     num_params = 2;
  161.     XtAppErrorMsg(XtWidgetToApplicationContext(w),
  162.         "childError", "class", "WidgetError",
  163.         "Children of class %s cannot be added to %n widgets",
  164.         params, &num_params);
  165.     }
  166.  
  167.     (*((CompositeWidgetClass)(menuWidgetClass->
  168.         core_class.superclass))->composite_class.insert_child) (w);
  169. }
  170.  
  171. static Boolean CvtStringToWidget(dpy, args, num_args, from, to, data)
  172.     Display *dpy;
  173.     XrmValuePtr args;
  174.     Cardinal *num_args;
  175.     XrmValuePtr from, to;
  176.     XtPointer *data;
  177. {
  178.     static Widget w;
  179.     Widget parent;
  180.     Boolean badConvert;
  181.  
  182.     if (*num_args != 1) {
  183.     XtAppErrorMsg(XtDisplayToApplicationContext(dpy),
  184.         "wrongParameters", "cvtStringToWidget",
  185.         "XtToolkitError",
  186.         "StringToWidget conversion needs parent arg",
  187.         (String *) NULL, (Cardinal *) NULL);
  188.     }
  189.  
  190.     /* Convert first arg into parent */
  191.  
  192.     parent = *(Widget*) args[0].addr;
  193.  
  194.     w = XtNameToWidget(parent, (String) from->addr);
  195.     badConvert = (w == NULL);
  196.  
  197.     if (badConvert) {
  198.         XtDisplayStringConversionWarning(dpy, from->addr, "Widget");
  199.     } else {
  200.     if (to->addr == NULL) to->addr = (caddr_t) &w;
  201.     else if (to->size < sizeof(Widget)) badConvert = TRUE;
  202.     else *(Widget *) to->addr = w;
  203.  
  204.     to->size = sizeof(Widget);
  205.     }
  206.     return !badConvert;
  207. }
  208.  
  209. static void ClassInitialize()
  210. {
  211.     static XtConvertArgRec parentConvertArgs[] = {
  212.     {XtBaseOffset, (XtPointer) XtOffsetOf(WidgetRec, core.parent),
  213.         sizeof(Widget)},
  214.     };
  215.  
  216.  
  217.     /* Register a converter for string to widget */
  218.  
  219.     XtSetTypeConverter(XtRString, XtRWidget, CvtStringToWidget,
  220.         parentConvertArgs, XtNumber(parentConvertArgs),
  221.         XtCacheNone, (XtDestructor) NULL);
  222. }
  223.  
  224. static void HandleMenuButton(w, client_data, event,
  225.     continue_to_dispatch)
  226.     Widget w;
  227.     XtPointer client_data;
  228.     XEvent *event;
  229.     Boolean *continue_to_dispatch;
  230. {
  231.     switch (event->type) {
  232.     case ButtonPress:
  233.         /* The new grab does an implicit AllowEvents */
  234.         (void) XtGrabPointer(w, True,
  235.             EnterWindowMask | LeaveWindowMask | 
  236.                 ButtonReleaseMask,
  237.             GrabModeAsync, GrabModeAsync, None, None,
  238.             event->xbutton.time);
  239.         break;
  240.  
  241.     case ButtonRelease:
  242.         /* Popping down also ungrabs the pointer */
  243.         XtPopdown(w);
  244.         break;
  245.     }
  246. }
  247.  
  248. static void Initialize(req, new)
  249.     Widget req, new;
  250. {
  251.     ((MenuWidget) new)->menu.save_border = -1;
  252.  
  253.     if (XtIsShell(XtParent(new))) {
  254.     XtAddRawEventHandler(XtParent(new),
  255.         ButtonPressMask | ButtonReleaseMask,
  256.         FALSE, HandleMenuButton, NULL);
  257.     }
  258. }
  259.  
  260. static void ConstraintInitialize(req, new, args, num_args)
  261.     Widget req, new;
  262.     ArgList args;
  263.     Cardinal *num_args;
  264. {
  265.     MenuConstraint mc = (MenuConstraint) new->core.constraints;
  266.  
  267.     mc->menu.desired_height = new->core.height;
  268.     mc->menu.desired_border_width = new->core.border_width;
  269. }
  270.  
  271. static void Destroy(w)
  272.     Widget w;
  273. {
  274.     XtRemoveRawEventHandler(XtParent(w), XtAllEvents, TRUE,
  275.         HandleMenuButton, NULL);
  276. }
  277.  
  278. static void PositionChildren(menu, initiator)
  279.     register MenuWidget menu;
  280.     Widget initiator;
  281. {
  282.     int i, y;
  283.     register int last_border;
  284.     register Widget child;
  285.     MenuConstraint mc;
  286.     Boolean first = TRUE;
  287.  
  288.     if (menu->composite.num_children == 0) return;
  289.  
  290.     for (i = 0; i < menu->composite.num_children; i++) {
  291.     child = menu->composite.children[i];
  292.     if (!XtIsManaged(child)) continue;
  293.     mc = (MenuConstraint) child->core.constraints;
  294.  
  295.     if (first) {
  296.         first = FALSE;
  297.         last_border = mc->menu.desired_border_width;
  298.         y = -last_border;
  299.     }
  300.  
  301.     if (child == initiator) {
  302.         if (last_border > child->core.border_width) {
  303.         y += last_border - child->core.border_width;
  304.         }
  305.         last_border = child->core.border_width;
  306.  
  307.         child->core.x = -last_border;
  308.         child->core.y = y;
  309.         child->core.width = menu->core.width;
  310.     } else {
  311.         if (last_border > mc->menu.desired_border_width) {
  312.         y += last_border - mc->menu.desired_border_width;
  313.         }
  314.         last_border = mc->menu.desired_border_width;
  315.  
  316.         XtConfigureWidget(child, -last_border, y,
  317.             menu->core.width, mc->menu.desired_height,
  318.             last_border);
  319.     }
  320.  
  321.     y += (int) child->core.height + last_border;
  322.     }
  323. }
  324.  
  325. static void Resize(w)
  326.     Widget w;
  327. {
  328.     PositionChildren((MenuWidget) w, (Widget) NULL);
  329. }
  330.  
  331. static int WidestDesiredSize(menu, initiator)
  332.     MenuWidget menu;
  333.     Widget initiator;
  334. {
  335.     register int i, width = 0;
  336.     register Widget child;
  337.     XtWidgetGeometry desired;
  338.  
  339.     for (i = 0; i < menu->composite.num_children; i++) {
  340.         child = menu->composite.children[i];
  341.     if (!XtIsManaged(child)) continue;
  342.  
  343.     if (child == initiator) {
  344.         if (child->core.width > width) {
  345.         width = child->core.width;
  346.         }
  347.     } else {
  348.         (void) XtQueryGeometry(child, NULL, &desired);
  349.         if (desired.width > width) width = desired.width;
  350.     }
  351.     }
  352.  
  353.     if (width <= 0) return 1;
  354.     else return width;
  355. }
  356.  
  357. static void CalculateDesiredSizes(menu, width, initiator)
  358.     MenuWidget menu;
  359.     Dimension width;
  360.     Widget initiator;
  361. {
  362.     XtWidgetGeometry proposed, desired;
  363.     register Widget child;
  364.     MenuConstraint mc;
  365.     register int i;
  366.  
  367.     for (i = 0; i < menu->composite.num_children; i++) {
  368.         child = menu->composite.children[i];
  369.     if (!XtIsManaged(child)) continue;
  370.     mc = (MenuConstraint) child->core.constraints;
  371.  
  372.     if (child == initiator) {
  373.         mc->menu.desired_height = child->core.height;
  374.         mc->menu.desired_border_width = 
  375.             child->core.border_width;
  376.     } else {
  377.         proposed.width = width;
  378.         proposed.request_mode = CWWidth;
  379.         (void) XtQueryGeometry(child, &proposed, &desired);
  380.  
  381.         mc->menu.desired_height = desired.height;
  382.         mc->menu.desired_border_width = desired.border_width;
  383.     }
  384.     }
  385. }
  386.  
  387. static void CalculateNewSize(menu, width, height, initiator)
  388.     register MenuWidget menu;
  389.     Dimension *width, *height;
  390.     Widget initiator;
  391. {
  392.     register int i;
  393.     register int last_border;
  394.     register Widget child;
  395.     int y;
  396.     MenuConstraint mc;
  397.     Boolean first = TRUE;
  398.  
  399.     if (menu->composite.num_children == 0) {
  400.     *width = *height = 10;    /* Arbitrary */
  401.     return;
  402.     }
  403.  
  404.     *width = WidestDesiredSize(menu, initiator);
  405.  
  406.     CalculateDesiredSizes(menu, *width, initiator);
  407.  
  408.     for (i = 0; i < menu->composite.num_children; i++) {
  409.         child = menu->composite.children[i];
  410.     if (!XtIsManaged(child)) continue;
  411.     mc = (MenuConstraint) child->core.constraints;
  412.  
  413.     if (first) {
  414.         first = FALSE;
  415.         last_border = mc->menu.desired_border_width;
  416.         y = -last_border;
  417.     }
  418.  
  419.     if (last_border > mc->menu.desired_border_width) {
  420.         y += last_border - (int) mc->menu.desired_border_width;
  421.     }
  422.     last_border = mc->menu.desired_border_width;
  423.     y += (int) mc->menu.desired_height + last_border;
  424.     }    
  425.     
  426.     if (y <= 0) y = 1;
  427.     *height = y;
  428. }
  429.  
  430. static void ChangeManaged(w)
  431.     Widget w;
  432. {
  433.     MenuWidget menu = (MenuWidget) w;
  434.     XtWidgetGeometry request;
  435.     XtGeometryResult result;
  436.  
  437.     CalculateNewSize(menu, &request.width, &request.height,
  438.         (Widget) NULL);
  439.  
  440.     if (request.width != menu->core.width ||
  441.         request.height != menu->core.height) {
  442.     request.request_mode = CWWidth | CWHeight;
  443.     do {
  444.         result = XtMakeGeometryRequest(w, &request, &request);
  445.     } while (result == XtGeometryAlmost);
  446.     }
  447.  
  448.     PositionChildren(menu, (Widget) NULL);
  449. }
  450.  
  451. static XtGeometryResult GeometryManager(w, desired, allowed)
  452.     Widget w;
  453.     XtWidgetGeometry *desired, *allowed;
  454. {
  455.     MenuWidget menu = (MenuWidget) XtParent(w);
  456.     XtWidgetGeometry request;
  457.     XtGeometryResult result;
  458.     Dimension save_width, save_height, save_border_width;
  459.  
  460. #define Wants(flag) (desired->request_mode & flag)
  461. #define RestoreGeometry(w) { \
  462.     w->core.width = save_width; \
  463.     w->core.height = save_height; \
  464.         w->core.border_width = save_border_width; }
  465.  
  466.     if (menu->menu.save_border != -1) {
  467.     /* This was caused by a child set-values */
  468.     w->core.border_width = menu->menu.save_border;
  469.     menu->menu.save_border = -1;
  470.     desired->border_width -= 1000;
  471.     }
  472.  
  473.     if (Wants(CWX) || Wants(CWY)) {
  474.     return XtGeometryNo;
  475.     }
  476.  
  477.     /* If only requesting a stack mode change, allow it */
  478.  
  479.     if (!Wants(CWWidth) && !Wants(CWHeight) && !Wants(CWBorderWidth)) {
  480.     return XtGeometryYes;
  481.     }
  482.  
  483.     /* Figure out how big we would be with this change */
  484.  
  485.     save_width = w->core.width;
  486.     save_height = w->core.height;
  487.     save_border_width = w->core.border_width;
  488.     if (Wants(CWWidth)) w->core.width = desired->width;
  489.     if (Wants(CWHeight)) w->core.height = desired->height;
  490.     if (Wants(CWBorderWidth)) {
  491.     w->core.border_width = desired->border_width;
  492.     }
  493.  
  494.     CalculateNewSize(menu, &request.width, &request.height, w);
  495.  
  496.     /* If the new width is the same as the old and the child requested
  497.        a width change, CalculateNewSize was not able to accommodate
  498.        the width change, so refuse the geometry request. */
  499.  
  500.     if (request.width == menu->core.width && Wants(CWWidth)) {
  501.     RestoreGeometry(w);
  502.     return XtGeometryNo;
  503.     }
  504.  
  505.     /* If new width is equal to child's width, we are going to try
  506.        to accommodate the child.  Make a geometry request.  This also
  507.        covers cases where the child requested no width
  508.        change since that wouldn't cause the menu to change width. */
  509.  
  510.     if (request.width == w->core.width) {
  511.     request.request_mode = CWWidth | CWHeight;
  512.     if (Wants(XtCWQueryOnly)) {
  513.         request.request_mode |= XtCWQueryOnly;
  514.     }
  515.     result = XtMakeGeometryRequest((Widget) menu, &request, NULL);
  516.  
  517.         /* Almost isn't good enough here; must be allowed */
  518.  
  519.     if (result == XtGeometryAlmost) result = XtGeometryNo;
  520.     if (result == XtGeometryNo || Wants(XtCWQueryOnly)) {
  521.         RestoreGeometry(w);
  522.     } else PositionChildren(menu, w);
  523.     return result;
  524.     }
  525.  
  526.     /* New width is different from child's width, so we want to return
  527.        XtGeometryAlmost.  See if this is allowed. */
  528.  
  529.     RestoreGeometry(w);
  530.  
  531.     request.request_mode = CWWidth | CWHeight | XtCWQueryOnly;
  532.     result = XtMakeGeometryRequest((Widget) menu, &request, NULL);
  533.  
  534.     /* Almost isn't good enough here; must be allowed */
  535.  
  536.     if (result != XtGeometryYes) return XtGeometryNo;
  537.  
  538.     /* It would be allowed, so return suggested geometry */
  539.  
  540.     *allowed = *desired;
  541.     allowed->width = request.width;
  542.     return XtGeometryAlmost;
  543. #undef Wants
  544. #undef RestoreGeometry
  545. }
  546.  
  547. static Boolean ConstraintSetValues(old, req, new, args, num_args)
  548.     Widget old, req, new;
  549.     ArgList args;
  550.     Cardinal *num_args;
  551. {
  552.     MenuConstraint newmc = (MenuConstraint) new->core.constraints;
  553.     MenuConstraint oldmc = (MenuConstraint) old->core.constraints;
  554.     register MenuWidget menu;
  555.     register int i, j;
  556.  
  557.     if (newmc->menu.insert_before != oldmc->menu.insert_before) {
  558.     menu = (MenuWidget) XtParent(new);
  559.  
  560.     /* Remove child from current position */
  561.  
  562.     for (i = 0; i < menu->composite.num_children &&
  563.         menu->composite.children[i] != new; i++) {}
  564.  
  565.     for (; i < menu->composite.num_children - 1; i++) {
  566.         menu->composite.children[i] =
  567.             menu->composite.children[i+1];
  568.     }
  569.  
  570.     /* Find new widget to insert before */
  571.  
  572.     for (i = 0; i < menu->composite.num_children - 1 &&
  573.         menu->composite.children[i] !=
  574.             newmc->menu.insert_before;
  575.         i++) {}
  576.  
  577.     /* Move the rest of them up */
  578.  
  579.     for (j = menu->composite.num_children - 1; j > i; j--) {
  580.         menu->composite.children[j] =
  581.             menu->composite.children[j-1];
  582.     }
  583.  
  584.     menu->composite.children[i] = new;
  585.     
  586.     /* Cause a geometry request */
  587.  
  588.     menu->menu.save_border = new->core.border_width;
  589.     new->core.border_width += 1000;
  590.     }
  591.  
  592.     return FALSE;
  593. }
  594.  
  595. static void Redisplay(w, event, region)
  596.     Widget w;
  597.     XEvent *event;
  598.     Region region;
  599. {
  600.     CompositeWidget comp = (CompositeWidget) w;
  601.     int i;
  602.     Widget c;        /* child */
  603.  
  604.     for (i = 0; i < comp->composite.num_children; i++) {
  605.     c = comp->composite.children[i];
  606.     if (XtIsManaged(c) && XtIsSubclass(c, labelGadgetClass) &&
  607.         XRectInRegion(region, c->core.x, c->core.y,
  608.             c->core.width + 2*c->core.border_width,
  609.             c->core.height + 2*c->core.border_width)
  610.             != RectangleOut) {
  611.         (*(XtClass(c)->core_class.expose))(c, event, region);
  612.         }
  613.     }
  614. }
  615.  
  616. static void ConstraintGetValuesHook(w, args, num_args)
  617.     Widget w;
  618.     ArgList args;
  619.     Cardinal *num_args;
  620. {
  621.     register int i, j;
  622.  
  623.     for (i = 0; i < *num_args; i++) {
  624.     if (strcmp(args[i].name, XtNinsertBefore) == 0) {
  625.         MenuConstraint mc = (MenuConstraint) w->core.constraints;
  626.         MenuWidget menu = (MenuWidget) XtParent(w);
  627.  
  628.         for (j = 0; j < menu->composite.num_children; j++) {
  629.         if (menu->composite.children[j] == w) {
  630.             if (j == menu->composite.num_children - 1) {
  631.             args[i].value = NULL;
  632.             } else {
  633.             *(Widget *) (args[i].value) = 
  634.                 menu->composite.children[j+1];
  635.             }
  636.             break;
  637.         }
  638.         } /* End of for loop checking children */
  639.     }
  640.     }
  641. }
  642.