1/* A general interface to the widgets of different toolkits. 2Copyright (C) 1992, 1993 Lucid, Inc. 3Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2003, 2004, 4 2005, 2006, 2007 Free Software Foundation, Inc. 5 6This file is part of the Lucid Widget Library. 7 8The Lucid Widget Library is free software; you can redistribute it and/or 9modify it under the terms of the GNU General Public License as published by 10the Free Software Foundation; either version 2, or (at your option) 11any later version. 12 13The Lucid Widget Library is distributed in the hope that it will be useful, 14but WITHOUT ANY WARRANTY; without even the implied warranty of 15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16GNU General Public License for more details. 17 18You should have received a copy of the GNU General Public License 19along with GNU Emacs; see the file COPYING. If not, write to 20the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21Boston, MA 02110-1301, USA. */ 22 23#ifdef NeXT 24#undef __STRICT_BSD__ /* ick */ 25#endif 26 27#ifdef HAVE_CONFIG_H 28#include <config.h> 29#endif 30 31#include "../src/lisp.h" 32 33#include <sys/types.h> 34#include <stdio.h> 35#include <ctype.h> 36#include "lwlib-int.h" 37#include "lwlib-utils.h" 38#include <X11/StringDefs.h> 39 40#if defined (USE_LUCID) 41#include "lwlib-Xlw.h" 42#endif 43#if defined (USE_MOTIF) 44#include "lwlib-Xm.h" 45#else /* not USE_MOTIF */ 46#if defined (USE_LUCID) 47#define USE_XAW 48#endif /* not USE_MOTIF && USE_LUCID */ 49#endif 50#if defined (USE_XAW) 51#include <X11/Xaw/Paned.h> 52#include "lwlib-Xaw.h" 53#endif 54 55#if !defined (USE_LUCID) && !defined (USE_MOTIF) 56 #error At least one of USE_LUCID or USE_MOTIF must be defined. 57#endif 58 59#ifndef max 60#define max(x, y) ((x) > (y) ? (x) : (y)) 61#endif 62 63/* List of all widgets managed by the library. */ 64static widget_info* 65all_widget_info = NULL; 66 67#ifdef USE_MOTIF 68char *lwlib_toolkit_type = "motif"; 69#else 70char *lwlib_toolkit_type = "lucid"; 71#endif 72 73static widget_value *merge_widget_value P_ ((widget_value *, 74 widget_value *, 75 int, int *)); 76static void instantiate_widget_instance P_ ((widget_instance *)); 77static int my_strcasecmp P_ ((char *, char *)); 78static void safe_free_str P_ ((char *)); 79static void free_widget_value_tree P_ ((widget_value *)); 80static widget_value *copy_widget_value_tree P_ ((widget_value *, 81 change_type)); 82static widget_info *allocate_widget_info P_ ((char *, char *, LWLIB_ID, 83 widget_value *, 84 lw_callback, lw_callback, 85 lw_callback, lw_callback)); 86static void free_widget_info P_ ((widget_info *)); 87static void mark_widget_destroyed P_ ((Widget, XtPointer, XtPointer)); 88static widget_instance *allocate_widget_instance P_ ((widget_info *, 89 Widget, Boolean)); 90static void free_widget_instance P_ ((widget_instance *)); 91static widget_info *get_widget_info P_ ((LWLIB_ID, Boolean)); 92static widget_instance *get_widget_instance P_ ((Widget, Boolean)); 93static widget_instance *find_instance P_ ((LWLIB_ID, Widget, Boolean)); 94static Boolean safe_strcmp P_ ((char *, char *)); 95static Widget name_to_widget P_ ((widget_instance *, char *)); 96static void set_one_value P_ ((widget_instance *, widget_value *, Boolean)); 97static void update_one_widget_instance P_ ((widget_instance *, Boolean)); 98static void update_all_widget_values P_ ((widget_info *, Boolean)); 99static void initialize_widget_instance P_ ((widget_instance *)); 100static widget_creation_function find_in_table P_ ((char *, widget_creation_entry *)); 101static Boolean dialog_spec_p P_ ((char *)); 102static void destroy_one_instance P_ ((widget_instance *)); 103static void lw_pop_all_widgets P_ ((LWLIB_ID, Boolean)); 104static Boolean get_one_value P_ ((widget_instance *, widget_value *)); 105static void show_one_widget_busy P_ ((Widget, Boolean)); 106 107void 108lwlib_memset (address, value, length) 109 char *address; 110 int value; 111 size_t length; 112{ 113 int i; 114 115 for (i = 0; i < length; i++) 116 address[i] = value; 117} 118 119void 120lwlib_bcopy (from, to, length) 121 char *from; 122 char *to; 123 int length; 124{ 125 int i; 126 127 for (i = 0; i < length; i++) 128 to[i] = from[i]; 129} 130/* utility functions for widget_instance and widget_info */ 131char * 132safe_strdup (s) 133 const char *s; 134{ 135 char *result; 136 if (! s) return 0; 137 result = (char *) malloc (strlen (s) + 1); 138 if (! result) 139 return 0; 140 strcpy (result, s); 141 return result; 142} 143 144/* Like strcmp but ignore differences in case. */ 145 146static int 147my_strcasecmp (s1, s2) 148 char *s1, *s2; 149{ 150 while (1) 151 { 152 int c1 = *s1++; 153 int c2 = *s2++; 154 if (isupper (c1)) 155 c1 = tolower (c1); 156 if (isupper (c2)) 157 c2 = tolower (c2); 158 if (c1 != c2) 159 return (c1 > c2 ? 1 : -1); 160 if (c1 == 0) 161 return 0; 162 } 163} 164 165static void 166safe_free_str (s) 167 char *s; 168{ 169 if (s) free (s); 170} 171 172static widget_value *widget_value_free_list = 0; 173static int malloc_cpt = 0; 174 175widget_value * 176malloc_widget_value () 177{ 178 widget_value *wv; 179 if (widget_value_free_list) 180 { 181 wv = widget_value_free_list; 182 widget_value_free_list = wv->free_list; 183 wv->free_list = 0; 184 } 185 else 186 { 187 wv = (widget_value *) malloc (sizeof (widget_value)); 188 malloc_cpt++; 189 } 190 lwlib_memset ((void*) wv, 0, sizeof (widget_value)); 191 return wv; 192} 193 194/* this is analogous to free(). It frees only what was allocated 195 by malloc_widget_value(), and no substructures. 196 */ 197void 198free_widget_value (wv) 199 widget_value *wv; 200{ 201 if (wv->free_list) 202 abort (); 203 204 if (malloc_cpt > 25) 205 { 206 /* When the number of already allocated cells is too big, 207 We free it. */ 208 free (wv); 209 malloc_cpt--; 210 } 211 else 212 { 213 wv->free_list = widget_value_free_list; 214 widget_value_free_list = wv; 215 } 216} 217 218static void 219free_widget_value_tree (wv) 220 widget_value *wv; 221{ 222 if (!wv) 223 return; 224 225 if (wv->name) free (wv->name); 226 if (wv->value) free (wv->value); 227 if (wv->key) free (wv->key); 228 229 wv->name = wv->value = wv->key = (char *) 0xDEADBEEF; 230 231 if (wv->toolkit_data && wv->free_toolkit_data) 232 { 233 XtFree (wv->toolkit_data); 234 wv->toolkit_data = (void *) 0xDEADBEEF; 235 } 236 237 if (wv->contents && (wv->contents != (widget_value*)1)) 238 { 239 free_widget_value_tree (wv->contents); 240 wv->contents = (widget_value *) 0xDEADBEEF; 241 } 242 if (wv->next) 243 { 244 free_widget_value_tree (wv->next); 245 wv->next = (widget_value *) 0xDEADBEEF; 246 } 247 free_widget_value (wv); 248} 249 250static widget_value * 251copy_widget_value_tree (val, change) 252 widget_value* val; 253 change_type change; 254{ 255 widget_value* copy; 256 257 if (!val) 258 return NULL; 259 if (val == (widget_value *) 1) 260 return val; 261 262 copy = malloc_widget_value (); 263 copy->name = safe_strdup (val->name); 264 copy->value = safe_strdup (val->value); 265 copy->key = safe_strdup (val->key); 266 copy->help = val->help; 267 copy->enabled = val->enabled; 268 copy->button_type = val->button_type; 269 copy->selected = val->selected; 270 copy->edited = False; 271 copy->change = change; 272 copy->this_one_change = change; 273 copy->contents = copy_widget_value_tree (val->contents, change); 274 copy->call_data = val->call_data; 275 copy->next = copy_widget_value_tree (val->next, change); 276 copy->toolkit_data = NULL; 277 copy->free_toolkit_data = False; 278 return copy; 279} 280 281static widget_info * 282allocate_widget_info (type, name, id, val, pre_activate_cb, 283 selection_cb, post_activate_cb, highlight_cb) 284 char* type; 285 char* name; 286 LWLIB_ID id; 287 widget_value* val; 288 lw_callback pre_activate_cb; 289 lw_callback selection_cb; 290 lw_callback post_activate_cb; 291 lw_callback highlight_cb; 292{ 293 widget_info* info = (widget_info*)malloc (sizeof (widget_info)); 294 info->type = safe_strdup (type); 295 info->name = safe_strdup (name); 296 info->id = id; 297 info->val = copy_widget_value_tree (val, STRUCTURAL_CHANGE); 298 info->busy = False; 299 info->pre_activate_cb = pre_activate_cb; 300 info->selection_cb = selection_cb; 301 info->post_activate_cb = post_activate_cb; 302 info->highlight_cb = highlight_cb; 303 info->instances = NULL; 304 305 info->next = all_widget_info; 306 all_widget_info = info; 307 308 return info; 309} 310 311static void 312free_widget_info (info) 313 widget_info* info; 314{ 315 safe_free_str (info->type); 316 safe_free_str (info->name); 317 free_widget_value_tree (info->val); 318 lwlib_memset ((void*)info, 0xDEADBEEF, sizeof (widget_info)); 319 free (info); 320} 321 322static void 323mark_widget_destroyed (widget, closure, call_data) 324 Widget widget; 325 XtPointer closure; 326 XtPointer call_data; 327{ 328 widget_instance* instance = (widget_instance*)closure; 329 330 /* be very conservative */ 331 if (instance->widget == widget) 332 instance->widget = NULL; 333} 334 335/* The messy #ifdef PROTOTYPES here and elsewhere are prompted by a 336 flood of warnings about argument promotion from proprietary ISO C 337 compilers. (etags still only makes one entry for each function.) */ 338static widget_instance * 339#ifdef PROTOTYPES 340allocate_widget_instance (widget_info* info, Widget parent, Boolean pop_up_p) 341#else 342allocate_widget_instance (info, parent, pop_up_p) 343 widget_info* info; 344 Widget parent; 345 Boolean pop_up_p; 346#endif 347{ 348 widget_instance* instance = 349 (widget_instance*)malloc (sizeof (widget_instance)); 350 bzero (instance, sizeof *instance); 351 instance->parent = parent; 352 instance->pop_up_p = pop_up_p; 353 instance->info = info; 354 instance->next = info->instances; 355 info->instances = instance; 356 357 instantiate_widget_instance (instance); 358 359 XtAddCallback (instance->widget, XtNdestroyCallback, 360 mark_widget_destroyed, (XtPointer)instance); 361 return instance; 362} 363 364static void 365free_widget_instance (instance) 366 widget_instance* instance; 367{ 368 lwlib_memset ((void*)instance, 0xDEADBEEF, sizeof (widget_instance)); 369 free (instance); 370} 371 372static widget_info * 373#ifdef PROTOTYPES 374get_widget_info (LWLIB_ID id, Boolean remove_p) 375#else 376get_widget_info (id, remove_p) 377 LWLIB_ID id; 378 Boolean remove_p; 379#endif 380{ 381 widget_info* info; 382 widget_info* prev; 383 for (prev = NULL, info = all_widget_info; 384 info; 385 prev = info, info = info->next) 386 if (info->id == id) 387 { 388 if (remove_p) 389 { 390 if (prev) 391 prev->next = info->next; 392 else 393 all_widget_info = info->next; 394 } 395 return info; 396 } 397 return NULL; 398} 399 400/* Internal function used by the library dependent implementation to get the 401 widget_value for a given widget in an instance */ 402widget_info * 403lw_get_widget_info (id) 404 LWLIB_ID id; 405{ 406 return get_widget_info (id, 0); 407} 408 409static widget_instance * 410#ifdef PROTOTYPES 411get_widget_instance (Widget widget, Boolean remove_p) 412#else 413get_widget_instance (widget, remove_p) 414 Widget widget; 415 Boolean remove_p; 416#endif 417{ 418 widget_info* info; 419 widget_instance* instance; 420 widget_instance* prev; 421 for (info = all_widget_info; info; info = info->next) 422 for (prev = NULL, instance = info->instances; 423 instance; 424 prev = instance, instance = instance->next) 425 if (instance->widget == widget) 426 { 427 if (remove_p) 428 { 429 if (prev) 430 prev->next = instance->next; 431 else 432 info->instances = instance->next; 433 } 434 return instance; 435 } 436 return (widget_instance *) 0; 437} 438 439/* Value is a pointer to the widget_instance corresponding to 440 WIDGET, or null if WIDGET is not a lwlib widget. */ 441 442widget_instance * 443lw_get_widget_instance (widget) 444 Widget widget; 445{ 446 return get_widget_instance (widget, False); 447} 448 449static widget_instance* 450#ifdef PROTOTYPES 451find_instance (LWLIB_ID id, Widget parent, Boolean pop_up_p) 452#else 453find_instance (id, parent, pop_up_p) 454 LWLIB_ID id; 455 Widget parent; 456 Boolean pop_up_p; 457#endif 458{ 459 widget_info* info = get_widget_info (id, False); 460 widget_instance* instance; 461 462 if (info) 463 for (instance = info->instances; instance; instance = instance->next) 464 if (instance->parent == parent && instance->pop_up_p == pop_up_p) 465 return instance; 466 467 return NULL; 468} 469 470 471/* utility function for widget_value */ 472static Boolean 473safe_strcmp (s1, s2) 474 char* s1; 475 char* s2; 476{ 477 if (!!s1 ^ !!s2) return True; 478 return (s1 && s2) ? strcmp (s1, s2) : s1 ? False : !!s2; 479} 480 481 482#if 0 483# define EXPLAIN(name, oc, nc, desc, a1, a2) \ 484 printf ("Change: \"%s\"\tmax(%s=%d,%s=%d)\t%s %d %d\n", \ 485 name, \ 486 (oc == NO_CHANGE ? "none" : \ 487 (oc == INVISIBLE_CHANGE ? "invisible" : \ 488 (oc == VISIBLE_CHANGE ? "visible" : \ 489 (oc == STRUCTURAL_CHANGE ? "structural" : "???")))), \ 490 oc, \ 491 (nc == NO_CHANGE ? "none" : \ 492 (nc == INVISIBLE_CHANGE ? "invisible" : \ 493 (nc == VISIBLE_CHANGE ? "visible" : \ 494 (nc == STRUCTURAL_CHANGE ? "structural" : "???")))), \ 495 nc, desc, a1, a2) 496#else 497# define EXPLAIN(name, oc, nc, desc, a1, a2) 498#endif 499 500 501static widget_value * 502merge_widget_value (val1, val2, level, change_p) 503 widget_value* val1; 504 widget_value* val2; 505 int level; 506 int *change_p; 507{ 508 change_type change, this_one_change; 509 widget_value* merged_next; 510 widget_value* merged_contents; 511 512 if (!val1) 513 { 514 if (val2) 515 { 516 *change_p = 1; 517 return copy_widget_value_tree (val2, STRUCTURAL_CHANGE); 518 } 519 else 520 return NULL; 521 } 522 if (!val2) 523 { 524 *change_p = 1; 525 free_widget_value_tree (val1); 526 return NULL; 527 } 528 529 change = NO_CHANGE; 530 531 if (safe_strcmp (val1->name, val2->name)) 532 { 533 EXPLAIN (val1->name, change, STRUCTURAL_CHANGE, "name change", 534 val1->name, val2->name); 535 change = max (change, STRUCTURAL_CHANGE); 536 safe_free_str (val1->name); 537 val1->name = safe_strdup (val2->name); 538 } 539 if (safe_strcmp (val1->value, val2->value)) 540 { 541 EXPLAIN (val1->name, change, VISIBLE_CHANGE, "value change", 542 val1->value, val2->value); 543 change = max (change, VISIBLE_CHANGE); 544 safe_free_str (val1->value); 545 val1->value = safe_strdup (val2->value); 546 } 547 if (safe_strcmp (val1->key, val2->key)) 548 { 549 EXPLAIN (val1->name, change, VISIBLE_CHANGE, "key change", 550 val1->key, val2->key); 551 change = max (change, VISIBLE_CHANGE); 552 safe_free_str (val1->key); 553 val1->key = safe_strdup (val2->key); 554 } 555 if (! EQ (val1->help, val2->help)) 556 { 557 EXPLAIN (val1->name, change, VISIBLE_CHANGE, "help change", 558 val1->help, val2->help); 559 change = max (change, VISIBLE_CHANGE); 560 val1->help = val2->help; 561 } 562 if (val1->enabled != val2->enabled) 563 { 564 EXPLAIN (val1->name, change, VISIBLE_CHANGE, "enablement change", 565 val1->enabled, val2->enabled); 566 change = max (change, VISIBLE_CHANGE); 567 val1->enabled = val2->enabled; 568 } 569 if (val1->button_type != val2->button_type) 570 { 571 EXPLAIN (val1->name, change, VISIBLE_CHANGE, "button type change", 572 val1->button_type, val2->button_type); 573 change = max (change, VISIBLE_CHANGE); 574 val1->button_type = val2->button_type; 575 } 576 if (val1->selected != val2->selected) 577 { 578 EXPLAIN (val1->name, change, VISIBLE_CHANGE, "selection change", 579 val1->selected, val2->selected); 580 change = max (change, VISIBLE_CHANGE); 581 val1->selected = val2->selected; 582 } 583 if (val1->call_data != val2->call_data) 584 { 585 EXPLAIN (val1->name, change, INVISIBLE_CHANGE, "call-data change", 586 val1->call_data, val2->call_data); 587 change = max (change, INVISIBLE_CHANGE); 588 val1->call_data = val2->call_data; 589 } 590 591 if (level > 0) 592 { 593 merged_contents = 594 merge_widget_value (val1->contents, val2->contents, level - 1, 595 change_p); 596 597 if (val1->contents && !merged_contents) 598 { 599 /* This used to say INVISIBLE_CHANGE, 600 but it is visible and vitally important when 601 the contents of the menu bar itself are entirely deleted. 602 603 But maybe it doesn't matter. This fails to fix the bug. */ 604 EXPLAIN (val1->name, change, STRUCTURAL_CHANGE, "(contents gone)", 605 0, 0); 606 change = max (change, STRUCTURAL_CHANGE); 607 } 608 else if (merged_contents && merged_contents->change != NO_CHANGE) 609 { 610 EXPLAIN (val1->name, change, INVISIBLE_CHANGE, "(contents change)", 611 0, 0); 612 change = max (change, INVISIBLE_CHANGE); 613#if 0 /* This was replaced by the August 9 1996 change in lwlib-Xm.c. */ 614#ifdef USE_MOTIF 615 change = max (merged_contents->change, change); 616#endif 617#endif 618 } 619 620 val1->contents = merged_contents; 621 } 622 623 this_one_change = change; 624 625 merged_next = merge_widget_value (val1->next, val2->next, level, change_p); 626 627 if (val1->next && !merged_next) 628 { 629 EXPLAIN (val1->name, change, STRUCTURAL_CHANGE, "(following gone)", 630 0, 0); 631 change = max (change, STRUCTURAL_CHANGE); 632 } 633 else if (merged_next) 634 { 635 if (merged_next->change) 636 EXPLAIN (val1->name, change, merged_next->change, "(following change)", 637 0, 0); 638 change = max (change, merged_next->change); 639 } 640 641 val1->next = merged_next; 642 643 val1->this_one_change = this_one_change; 644 val1->change = change; 645 646 if (change > NO_CHANGE && val1->toolkit_data) 647 { 648 *change_p = 1; 649 if (val1->free_toolkit_data) 650 XtFree (val1->toolkit_data); 651 val1->toolkit_data = NULL; 652 } 653 654 return val1; 655} 656 657 658/* modifying the widgets */ 659static Widget 660name_to_widget (instance, name) 661 widget_instance* instance; 662 char* name; 663{ 664 Widget widget = NULL; 665 666 if (!instance->widget) 667 return NULL; 668 669 if (!strcmp (XtName (instance->widget), name)) 670 widget = instance->widget; 671 else 672 { 673 int length = strlen (name) + 2; 674 char* real_name = (char *) xmalloc (length); 675 real_name [0] = '*'; 676 strcpy (real_name + 1, name); 677 678 widget = XtNameToWidget (instance->widget, real_name); 679 680 free (real_name); 681 } 682 return widget; 683} 684 685static void 686#ifdef PROTOTYPES 687set_one_value (widget_instance* instance, widget_value* val, Boolean deep_p) 688#else 689set_one_value (instance, val, deep_p) 690 widget_instance* instance; 691 widget_value* val; 692 Boolean deep_p; 693#endif 694{ 695 Widget widget = name_to_widget (instance, val->name); 696 697 if (widget) 698 { 699#if defined (USE_LUCID) 700 if (lw_lucid_widget_p (instance->widget)) 701 xlw_update_one_widget (instance, widget, val, deep_p); 702#endif 703#if defined (USE_MOTIF) 704 if (lw_motif_widget_p (instance->widget)) 705 xm_update_one_widget (instance, widget, val, deep_p); 706#endif 707#if defined (USE_XAW) 708 if (lw_xaw_widget_p (instance->widget)) 709 xaw_update_one_widget (instance, widget, val, deep_p); 710#endif 711 } 712} 713 714static void 715#ifdef PROTOTYPES 716update_one_widget_instance (widget_instance* instance, Boolean deep_p) 717#else 718update_one_widget_instance (instance, deep_p) 719 widget_instance* instance; 720 Boolean deep_p; 721#endif 722{ 723 widget_value *val; 724 725 if (!instance->widget) 726 /* the widget was destroyed */ 727 return; 728 729 for (val = instance->info->val; val; val = val->next) 730 if (val->change != NO_CHANGE) 731 set_one_value (instance, val, deep_p); 732} 733 734static void 735#ifdef PROTOTYPES 736update_all_widget_values (widget_info* info, Boolean deep_p) 737#else 738update_all_widget_values (info, deep_p) 739 widget_info* info; 740 Boolean deep_p; 741#endif 742{ 743 widget_instance* instance; 744 widget_value* val; 745 746 for (instance = info->instances; instance; instance = instance->next) 747 update_one_widget_instance (instance, deep_p); 748 749 for (val = info->val; val; val = val->next) 750 val->change = NO_CHANGE; 751} 752 753int 754#ifdef PROTOTYPES 755lw_modify_all_widgets (LWLIB_ID id, widget_value* val, Boolean deep_p) 756#else 757lw_modify_all_widgets (id, val, deep_p) 758 LWLIB_ID id; 759 widget_value* val; 760 Boolean deep_p; 761#endif 762{ 763 widget_info* info = get_widget_info (id, False); 764 widget_value* new_val; 765 widget_value* next_new_val; 766 widget_value* cur; 767 widget_value* prev; 768 widget_value* next; 769 int found; 770 int change_p = 0; 771 772 if (!info) 773 return 0; 774 775 for (new_val = val; new_val; new_val = new_val->next) 776 { 777 next_new_val = new_val->next; 778 new_val->next = NULL; 779 found = False; 780 for (prev = NULL, cur = info->val; cur; prev = cur, cur = cur->next) 781 if (!strcmp (cur->name, new_val->name)) 782 { 783 found = True; 784 next = cur->next; 785 cur->next = NULL; 786 cur = merge_widget_value (cur, new_val, deep_p ? 1000 : 1, 787 &change_p); 788 if (prev) 789 prev->next = cur ? cur : next; 790 else 791 info->val = cur ? cur : next; 792 if (cur) 793 cur->next = next; 794 break; 795 } 796 if (!found) 797 { 798 /* Could not find it, add it */ 799 if (prev) 800 prev->next = copy_widget_value_tree (new_val, STRUCTURAL_CHANGE); 801 else 802 info->val = copy_widget_value_tree (new_val, STRUCTURAL_CHANGE); 803 change_p = 1; 804 } 805 new_val->next = next_new_val; 806 } 807 808 update_all_widget_values (info, deep_p); 809 return change_p; 810} 811 812 813/* creating the widgets */ 814 815static void 816initialize_widget_instance (instance) 817 widget_instance* instance; 818{ 819 widget_value* val; 820 821 for (val = instance->info->val; val; val = val->next) 822 val->change = STRUCTURAL_CHANGE; 823 824 update_one_widget_instance (instance, True); 825 826 for (val = instance->info->val; val; val = val->next) 827 val->change = NO_CHANGE; 828} 829 830 831static widget_creation_function 832find_in_table (type, table) 833 char* type; 834 widget_creation_entry* table; 835{ 836 widget_creation_entry* cur; 837 for (cur = table; cur->type; cur++) 838 if (!my_strcasecmp (type, cur->type)) 839 return cur->function; 840 return NULL; 841} 842 843static Boolean 844dialog_spec_p (name) 845 char* name; 846{ 847 /* return True if name matches [EILPQeilpq][1-9][Bb] or 848 [EILPQeilpq][1-9][Bb][Rr][1-9] */ 849 if (!name) 850 return False; 851 852 switch (name [0]) 853 { 854 case 'E': case 'I': case 'L': case 'P': case 'Q': 855 case 'e': case 'i': case 'l': case 'p': case 'q': 856 if (name [1] >= '0' && name [1] <= '9') 857 { 858 if (name [2] != 'B' && name [2] != 'b') 859 return False; 860 if (!name [3]) 861 return True; 862 if ((name [3] == 'T' || name [3] == 't') && !name [4]) 863 return True; 864 if ((name [3] == 'R' || name [3] == 'r') 865 && name [4] >= '0' && name [4] <= '9' && !name [5]) 866 return True; 867 return False; 868 } 869 else 870 return False; 871 872 default: 873 return False; 874 } 875} 876 877static void 878instantiate_widget_instance (instance) 879 widget_instance* instance; 880{ 881 widget_creation_function function = NULL; 882 883#if defined (USE_LUCID) 884 if (!function) 885 function = find_in_table (instance->info->type, xlw_creation_table); 886#endif 887#if defined(USE_MOTIF) 888 if (!function) 889 function = find_in_table (instance->info->type, xm_creation_table); 890#endif 891#if defined (USE_XAW) 892 if (!function) 893 function = find_in_table (instance->info->type, xaw_creation_table); 894#endif 895 896 if (!function) 897 { 898 if (dialog_spec_p (instance->info->type)) 899 { 900#if defined (USE_LUCID) 901 /* not yet */ 902#endif 903#if defined(USE_MOTIF) 904 if (!function) 905 function = xm_create_dialog; 906#endif 907#if defined (USE_XAW) 908 if (!function) 909 function = xaw_create_dialog; 910#endif 911 } 912 } 913 914 if (!function) 915 { 916 printf ("No creation function for widget type %s\n", 917 instance->info->type); 918 abort (); 919 } 920 921 instance->widget = (*function) (instance); 922 923 if (!instance->widget) 924 abort (); 925 926 /* XtRealizeWidget (instance->widget);*/ 927} 928 929void 930lw_register_widget (type, name, id, val, pre_activate_cb, 931 selection_cb, post_activate_cb, highlight_cb) 932 char* type; 933 char* name; 934 LWLIB_ID id; 935 widget_value* val; 936 lw_callback pre_activate_cb; 937 lw_callback selection_cb; 938 lw_callback post_activate_cb; 939 lw_callback highlight_cb; 940{ 941 if (!get_widget_info (id, False)) 942 allocate_widget_info (type, name, id, val, pre_activate_cb, selection_cb, 943 post_activate_cb, highlight_cb); 944} 945 946Widget 947#ifdef PROTOTYPES 948lw_get_widget (LWLIB_ID id, Widget parent, Boolean pop_up_p) 949#else 950lw_get_widget (id, parent, pop_up_p) 951 LWLIB_ID id; 952 Widget parent; 953 Boolean pop_up_p; 954#endif 955{ 956 widget_instance* instance; 957 958 instance = find_instance (id, parent, pop_up_p); 959 return instance ? instance->widget : NULL; 960} 961 962Widget 963#ifdef PROTOTYPES 964lw_make_widget (LWLIB_ID id, Widget parent, Boolean pop_up_p) 965#else 966lw_make_widget (id, parent, pop_up_p) 967 LWLIB_ID id; 968 Widget parent; 969 Boolean pop_up_p; 970#endif 971{ 972 widget_instance* instance; 973 widget_info* info; 974 975 instance = find_instance (id, parent, pop_up_p); 976 if (!instance) 977 { 978 info = get_widget_info (id, False); 979 if (!info) 980 return NULL; 981 instance = allocate_widget_instance (info, parent, pop_up_p); 982 initialize_widget_instance (instance); 983 } 984 if (!instance->widget) 985 abort (); 986 return instance->widget; 987} 988 989Widget 990#ifdef PROTOTYPES 991lw_create_widget (char* type, char* name, LWLIB_ID id, widget_value* val, 992 Widget parent, Boolean pop_up_p, 993 lw_callback pre_activate_cb, lw_callback selection_cb, 994 lw_callback post_activate_cb, lw_callback highlight_cb) 995#else 996lw_create_widget (type, name, id, val, parent, pop_up_p, pre_activate_cb, 997 selection_cb, post_activate_cb, highlight_cb) 998 char* type; 999 char* name; 1000 LWLIB_ID id; 1001 widget_value* val; 1002 Widget parent; 1003 Boolean pop_up_p; 1004 lw_callback pre_activate_cb; 1005 lw_callback selection_cb; 1006 lw_callback post_activate_cb; 1007 lw_callback highlight_cb; 1008#endif 1009{ 1010 lw_register_widget (type, name, id, val, pre_activate_cb, selection_cb, 1011 post_activate_cb, highlight_cb); 1012 return lw_make_widget (id, parent, pop_up_p); 1013} 1014 1015 1016/* destroying the widgets */ 1017static void 1018destroy_one_instance (instance) 1019 widget_instance* instance; 1020{ 1021 /* Remove the destroy callback on the widget; that callback will try to 1022 dereference the instance object (to set its widget slot to 0, since the 1023 widget is dead.) Since the instance is now dead, we don't have to worry 1024 about the fact that its widget is dead too. 1025 1026 This happens in the Phase2Destroy of the widget, so this callback would 1027 not have been run until arbitrarily long after the instance was freed. 1028 */ 1029 if (instance->widget) 1030 XtRemoveCallback (instance->widget, XtNdestroyCallback, 1031 mark_widget_destroyed, (XtPointer)instance); 1032 1033 if (instance->widget) 1034 { 1035 /* The else are pretty tricky here, including the empty statement 1036 at the end because it would be very bad to destroy a widget 1037 twice. */ 1038#if defined (USE_LUCID) 1039 if (lw_lucid_widget_p (instance->widget)) 1040 xlw_destroy_instance (instance); 1041 else 1042#endif 1043#if defined (USE_MOTIF) 1044 if (lw_motif_widget_p (instance->widget)) 1045 xm_destroy_instance (instance); 1046 else 1047#endif 1048#if defined (USE_XAW) 1049 if (lw_xaw_widget_p (instance->widget)) 1050 xaw_destroy_instance (instance); 1051 else 1052#endif 1053 /* do not remove the empty statement */ 1054 ; 1055 } 1056 1057 free_widget_instance (instance); 1058} 1059 1060void 1061lw_destroy_widget (w) 1062 Widget w; 1063{ 1064 widget_instance* instance = get_widget_instance (w, True); 1065 1066 if (instance) 1067 { 1068 widget_info *info = instance->info; 1069 /* instance has already been removed from the list; free it */ 1070 destroy_one_instance (instance); 1071 /* if there are no instances left, free the info too */ 1072 if (!info->instances) 1073 lw_destroy_all_widgets (info->id); 1074 } 1075} 1076 1077void 1078lw_destroy_all_widgets (id) 1079 LWLIB_ID id; 1080{ 1081 widget_info* info = get_widget_info (id, True); 1082 widget_instance* instance; 1083 widget_instance* next; 1084 1085 if (info) 1086 { 1087 for (instance = info->instances; instance; ) 1088 { 1089 next = instance->next; 1090 destroy_one_instance (instance); 1091 instance = next; 1092 } 1093 free_widget_info (info); 1094 } 1095} 1096 1097void 1098lw_destroy_everything () 1099{ 1100 while (all_widget_info) 1101 lw_destroy_all_widgets (all_widget_info->id); 1102} 1103 1104void 1105lw_destroy_all_pop_ups () 1106{ 1107 widget_info* info; 1108 widget_info* next; 1109 widget_instance* instance; 1110 1111 for (info = all_widget_info; info; info = next) 1112 { 1113 next = info->next; 1114 instance = info->instances; 1115 if (instance && instance->pop_up_p) 1116 lw_destroy_all_widgets (info->id); 1117 } 1118} 1119 1120#ifdef USE_MOTIF 1121extern Widget first_child (/* Widget */); /* garbage */ 1122#endif 1123 1124Widget 1125lw_raise_all_pop_up_widgets () 1126{ 1127 widget_info* info; 1128 widget_instance* instance; 1129 Widget result = NULL; 1130 1131 for (info = all_widget_info; info; info = info->next) 1132 for (instance = info->instances; instance; instance = instance->next) 1133 if (instance->pop_up_p) 1134 { 1135 Widget widget = instance->widget; 1136 if (widget) 1137 { 1138 if (XtIsManaged (widget) 1139#ifdef USE_MOTIF 1140 /* What a complete load of crap!!!! 1141 When a dialogShell is on the screen, it is not managed! 1142 */ 1143 || (lw_motif_widget_p (instance->widget) && 1144 XtIsManaged (first_child (widget))) 1145#endif 1146 ) 1147 { 1148 if (!result) 1149 result = widget; 1150 XMapRaised (XtDisplay (widget), XtWindow (widget)); 1151 } 1152 } 1153 } 1154 return result; 1155} 1156 1157static void 1158#ifdef PROTOTYPES 1159lw_pop_all_widgets (LWLIB_ID id, Boolean up) 1160#else 1161lw_pop_all_widgets (id, up) 1162 LWLIB_ID id; 1163 Boolean up; 1164#endif 1165{ 1166 widget_info* info = get_widget_info (id, False); 1167 widget_instance* instance; 1168 1169 if (info) 1170 for (instance = info->instances; instance; instance = instance->next) 1171 if (instance->pop_up_p && instance->widget) 1172 { 1173#if defined (USE_LUCID) 1174 if (lw_lucid_widget_p (instance->widget)) 1175 { 1176 XtRealizeWidget (instance->widget); 1177 xlw_pop_instance (instance, up); 1178 } 1179#endif 1180#if defined (USE_MOTIF) 1181 if (lw_motif_widget_p (instance->widget)) 1182 { 1183 XtRealizeWidget (instance->widget); 1184 xm_pop_instance (instance, up); 1185 } 1186#endif 1187#if defined (USE_XAW) 1188 if (lw_xaw_widget_p (instance->widget)) 1189 { 1190 XtRealizeWidget (XtParent (instance->widget)); 1191 XtRealizeWidget (instance->widget); 1192 xaw_pop_instance (instance, up); 1193 } 1194#endif 1195 } 1196} 1197 1198void 1199lw_pop_up_all_widgets (id) 1200 LWLIB_ID id; 1201{ 1202 lw_pop_all_widgets (id, True); 1203} 1204 1205void 1206lw_pop_down_all_widgets (id) 1207 LWLIB_ID id; 1208{ 1209 lw_pop_all_widgets (id, False); 1210} 1211 1212void 1213lw_popup_menu (widget, event) 1214 Widget widget; 1215 XEvent *event; 1216{ 1217#if defined (USE_LUCID) 1218 if (lw_lucid_widget_p (widget)) 1219 xlw_popup_menu (widget, event); 1220#endif 1221#if defined (USE_MOTIF) 1222 if (lw_motif_widget_p (widget)) 1223 xm_popup_menu (widget, event); 1224#endif 1225#if defined (USE_XAW) 1226 if (lw_xaw_widget_p (widget)) 1227 xaw_popup_menu (widget, event); 1228#endif 1229} 1230 1231/* get the values back */ 1232static Boolean 1233get_one_value (instance, val) 1234 widget_instance* instance; 1235 widget_value* val; 1236{ 1237 Widget widget = name_to_widget (instance, val->name); 1238 1239 if (widget) 1240 { 1241#if defined (USE_LUCID) 1242 if (lw_lucid_widget_p (instance->widget)) 1243 xlw_update_one_value (instance, widget, val); 1244#endif 1245#if defined (USE_MOTIF) 1246 if (lw_motif_widget_p (instance->widget)) 1247 xm_update_one_value (instance, widget, val); 1248#endif 1249#if defined (USE_XAW) 1250 if (lw_xaw_widget_p (instance->widget)) 1251 xaw_update_one_value (instance, widget, val); 1252#endif 1253 return True; 1254 } 1255 else 1256 return False; 1257} 1258 1259Boolean 1260lw_get_some_values (id, val_out) 1261 LWLIB_ID id; 1262 widget_value* val_out; 1263{ 1264 widget_info* info = get_widget_info (id, False); 1265 widget_instance* instance; 1266 widget_value* val; 1267 Boolean result = False; 1268 1269 if (!info) 1270 return False; 1271 1272 instance = info->instances; 1273 if (!instance) 1274 return False; 1275 1276 for (val = val_out; val; val = val->next) 1277 if (get_one_value (instance, val)) 1278 result = True; 1279 1280 return result; 1281} 1282 1283widget_value* 1284lw_get_all_values (id) 1285 LWLIB_ID id; 1286{ 1287 widget_info* info = get_widget_info (id, False); 1288 widget_value* val = info->val; 1289 if (lw_get_some_values (id, val)) 1290 return val; 1291 else 1292 return NULL; 1293} 1294 1295/* internal function used by the library dependent implementation to get the 1296 widget_value for a given widget in an instance */ 1297widget_value* 1298lw_get_widget_value_for_widget (instance, w) 1299 widget_instance* instance; 1300 Widget w; 1301{ 1302 char* name = XtName (w); 1303 widget_value* cur; 1304 for (cur = instance->info->val; cur; cur = cur->next) 1305 if (!strcmp (cur->name, name)) 1306 return cur; 1307 return NULL; 1308} 1309 1310/* update other instances value when one thing changed */ 1311 1312/* To forbid recursive calls */ 1313static Boolean lwlib_updating; 1314 1315/* This function can be used as a an XtCallback for the widgets that get 1316 modified to update other instances of the widgets. Closure should be the 1317 widget_instance. */ 1318void 1319lw_internal_update_other_instances (widget, closure, call_data) 1320 Widget widget; 1321 XtPointer closure; 1322 XtPointer call_data; 1323{ 1324 widget_instance* instance = (widget_instance*)closure; 1325 char* name = XtName (widget); 1326 widget_info* info; 1327 widget_instance* cur; 1328 widget_value* val; 1329 1330 /* Avoid possibly infinite recursion. */ 1331 if (lwlib_updating) 1332 return; 1333 1334 /* protect against the widget being destroyed */ 1335 if (XtWidgetBeingDestroyedP (widget)) 1336 return; 1337 1338 /* Return immediately if there are no other instances */ 1339 info = instance->info; 1340 if (!info->instances->next) 1341 return; 1342 1343 lwlib_updating = True; 1344 1345 for (val = info->val; val && strcmp (val->name, name); val = val->next); 1346 1347 if (val && get_one_value (instance, val)) 1348 for (cur = info->instances; cur; cur = cur->next) 1349 if (cur != instance) 1350 set_one_value (cur, val, True); 1351 1352 lwlib_updating = False; 1353} 1354 1355 1356/* get the id */ 1357 1358LWLIB_ID 1359lw_get_widget_id (w) 1360 Widget w; 1361{ 1362 widget_instance* instance = get_widget_instance (w, False); 1363 1364 return instance ? instance->info->id : 0; 1365} 1366 1367/* set the keyboard focus */ 1368void 1369lw_set_keyboard_focus (parent, w) 1370 Widget parent; 1371 Widget w; 1372{ 1373#if defined (USE_MOTIF) 1374 xm_set_keyboard_focus (parent, w); 1375#else 1376 XtSetKeyboardFocus (parent, w); 1377#endif 1378} 1379 1380/* Show busy */ 1381static void 1382#ifdef PROTOTYPES 1383show_one_widget_busy (Widget w, Boolean flag) 1384#else 1385show_one_widget_busy (w, flag) 1386 Widget w; 1387 Boolean flag; 1388#endif 1389{ 1390 Pixel foreground = 0; 1391 Pixel background = 1; 1392 Widget widget_to_invert = XtNameToWidget (w, "*sheet"); 1393 if (!widget_to_invert) 1394 widget_to_invert = w; 1395 1396 XtVaGetValues (widget_to_invert, 1397 XtNforeground, &foreground, 1398 XtNbackground, &background, 1399 NULL); 1400 XtVaSetValues (widget_to_invert, 1401 XtNforeground, background, 1402 XtNbackground, foreground, 1403 NULL); 1404} 1405 1406void 1407#ifdef PROTOTYPES 1408lw_show_busy (Widget w, Boolean busy) 1409#else 1410lw_show_busy (w, busy) 1411 Widget w; 1412 Boolean busy; 1413#endif 1414{ 1415 widget_instance* instance = get_widget_instance (w, False); 1416 widget_info* info; 1417 widget_instance* next; 1418 1419 if (instance) 1420 { 1421 info = instance->info; 1422 if (info->busy != busy) 1423 { 1424 for (next = info->instances; next; next = next->next) 1425 if (next->widget) 1426 show_one_widget_busy (next->widget, busy); 1427 info->busy = busy; 1428 } 1429 } 1430} 1431 1432/* This hack exists because Lucid/Athena need to execute the strange 1433 function below to support geometry management. */ 1434void 1435#ifdef PROTOTYPES 1436lw_refigure_widget (Widget w, Boolean doit) 1437#else 1438lw_refigure_widget (w, doit) 1439 Widget w; 1440 Boolean doit; 1441#endif 1442{ 1443#if defined (USE_XAW) 1444 XawPanedSetRefigureMode (w, doit); 1445#endif 1446#if defined (USE_MOTIF) 1447 if (doit) 1448 XtManageChild (w); 1449 else 1450 XtUnmanageChild (w); 1451#endif 1452} 1453 1454/* Toolkit independent way of determining if an event window is in the 1455 menubar. */ 1456Boolean 1457lw_window_is_in_menubar (win, menubar_widget) 1458 Window win; 1459 Widget menubar_widget; 1460{ 1461 return menubar_widget 1462#if defined (USE_LUCID) 1463 && XtWindow (menubar_widget) == win; 1464#endif 1465#if defined (USE_MOTIF) 1466 && ((XtWindow (menubar_widget) == win) 1467 || (XtWindowToWidget (XtDisplay (menubar_widget), win) 1468 && (XtParent (XtWindowToWidget (XtDisplay (menubar_widget), win)) 1469 == menubar_widget))); 1470#endif 1471} 1472 1473/* Motif hack to set the main window areas. */ 1474void 1475lw_set_main_areas (parent, menubar, work_area) 1476 Widget parent; 1477 Widget menubar; 1478 Widget work_area; 1479{ 1480#if defined (USE_MOTIF) 1481 xm_set_main_areas (parent, menubar, work_area); 1482#endif 1483} 1484 1485/* Manage resizing for Motif. This disables resizing when the menubar 1486 is about to be modified. */ 1487void 1488#ifdef PROTOTYPES 1489lw_allow_resizing (Widget w, Boolean flag) 1490#else 1491lw_allow_resizing (w, flag) 1492 Widget w; 1493 Boolean flag; 1494#endif 1495{ 1496#if defined (USE_MOTIF) 1497 xm_manage_resizing (w, flag); 1498#endif 1499} 1500 1501 1502/* Value is non-zero if LABEL is a menu separator. If it is, *TYPE is 1503 set to an appropriate enumerator of type enum menu_separator. 1504 MOTIF_P non-zero means map separator types not supported by Motif 1505 to similar ones that are supported. */ 1506 1507int 1508lw_separator_p (label, type, motif_p) 1509 char *label; 1510 enum menu_separator *type; 1511 int motif_p; 1512{ 1513 int separator_p = 0; 1514 1515 if (strlen (label) >= 3 1516 && bcmp (label, "--:", 3) == 0) 1517 { 1518 static struct separator_table 1519 { 1520 char *name; 1521 enum menu_separator type; 1522 } 1523 separator_names[] = 1524 { 1525 {"space", SEPARATOR_NO_LINE}, 1526 {"noLine", SEPARATOR_NO_LINE}, 1527 {"singleLine", SEPARATOR_SINGLE_LINE}, 1528 {"doubleLine", SEPARATOR_DOUBLE_LINE}, 1529 {"singleDashedLine", SEPARATOR_SINGLE_DASHED_LINE}, 1530 {"doubleDashedLine", SEPARATOR_DOUBLE_DASHED_LINE}, 1531 {"shadowEtchedIn", SEPARATOR_SHADOW_ETCHED_IN}, 1532 {"shadowEtchedOut", SEPARATOR_SHADOW_ETCHED_OUT}, 1533 {"shadowEtchedInDash", SEPARATOR_SHADOW_ETCHED_IN_DASH}, 1534 {"shadowEtchedOutDash", SEPARATOR_SHADOW_ETCHED_OUT_DASH}, 1535 {"shadowDoubleEtchedIn", SEPARATOR_SHADOW_DOUBLE_ETCHED_IN}, 1536 {"shadowDoubleEtchedOut", SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT}, 1537 {"shadowDoubleEtchedInDash", SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH}, 1538 {"shadowDoubleEtchedOutDash", SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH}, 1539 {0,0} 1540 }; 1541 1542 int i; 1543 1544 label += 3; 1545 for (i = 0; separator_names[i].name; ++i) 1546 if (strcmp (label, separator_names[i].name) == 0) 1547 { 1548 separator_p = 1; 1549 *type = separator_names[i].type; 1550 1551 /* If separator type is not supported under Motif, 1552 use a similar one. */ 1553 if (motif_p && *type >= SEPARATOR_SHADOW_DOUBLE_ETCHED_IN) 1554 *type -= 4; 1555 break; 1556 } 1557 } 1558 else if (strlen (label) > 3 1559 && bcmp (label, "--", 2) == 0 1560 && label[2] != '-') 1561 { 1562 /* Alternative, more Emacs-style names. */ 1563 static struct separator_table 1564 { 1565 char *name; 1566 enum menu_separator type; 1567 } 1568 separator_names[] = 1569 { 1570 {"space", SEPARATOR_NO_LINE}, 1571 {"no-line", SEPARATOR_NO_LINE}, 1572 {"single-line", SEPARATOR_SINGLE_LINE}, 1573 {"double-line", SEPARATOR_DOUBLE_LINE}, 1574 {"single-dashed-line", SEPARATOR_SINGLE_DASHED_LINE}, 1575 {"double-dashed-line", SEPARATOR_DOUBLE_DASHED_LINE}, 1576 {"shadow-etched-in", SEPARATOR_SHADOW_ETCHED_IN}, 1577 {"shadow-etched-out", SEPARATOR_SHADOW_ETCHED_OUT}, 1578 {"shadow-etched-in-dash", SEPARATOR_SHADOW_ETCHED_IN_DASH}, 1579 {"shadow-etched-out-dash", SEPARATOR_SHADOW_ETCHED_OUT_DASH}, 1580 {"shadow-double-etched-in", SEPARATOR_SHADOW_DOUBLE_ETCHED_IN}, 1581 {"shadow-double-etched-out", SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT}, 1582 {"shadow-double-etched-in-dash", SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH}, 1583 {"shadow-double-etched-out-dash",SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH}, 1584 {0,0} 1585 }; 1586 1587 int i; 1588 1589 label += 2; 1590 for (i = 0; separator_names[i].name; ++i) 1591 if (strcmp (label, separator_names[i].name) == 0) 1592 { 1593 separator_p = 1; 1594 *type = separator_names[i].type; 1595 1596 /* If separator type is not supported under Motif, 1597 use a similar one. */ 1598 if (motif_p && *type >= SEPARATOR_SHADOW_DOUBLE_ETCHED_IN) 1599 *type -= 4; 1600 break; 1601 } 1602 } 1603 else 1604 { 1605 /* Old-style separator, maybe. It's a separator if it contains 1606 only dashes. */ 1607 while (*label == '-') 1608 ++label; 1609 separator_p = *label == 0; 1610 *type = SEPARATOR_SHADOW_ETCHED_IN; 1611 } 1612 1613 return separator_p; 1614} 1615 1616/* arch-tag: 3d730f36-a441-4a71-9971-48ef3b5a4d9f 1617 (do not change this comment) */ 1618