| 31 #include <inttypes.h> |
31 #include <inttypes.h> |
| 32 #include <stdarg.h> |
32 #include <stdarg.h> |
| 33 |
33 |
| 34 #include "menu.h" |
34 #include "menu.h" |
| 35 #include "toolkit.h" |
35 #include "toolkit.h" |
| |
36 #include "widget.h" |
| 36 #include "../common/context.h" |
37 #include "../common/context.h" |
| 37 #include "../common/menu.h" |
38 #include "../common/menu.h" |
| 38 #include "../common/types.h" |
39 #include "../common/types.h" |
| 39 #include "../ui/properties.h" |
40 #include "../ui/properties.h" |
| 40 #include "../ui/window.h" |
41 #include "../ui/window.h" |
| 41 #include "container.h" |
42 #include "container.h" |
| 42 |
43 |
| 43 #include <cx/linked_list.h> |
44 #include <cx/linked_list.h> |
| 44 #include <cx/array_list.h> |
45 #include <cx/array_list.h> |
| |
46 #include <cx/printf.h> |
| 45 |
47 |
| 46 #if GTK_MAJOR_VERSION <= 3 |
48 #if GTK_MAJOR_VERSION <= 3 |
| 47 |
49 |
| 48 static ui_menu_add_f createMenuItem[] = { |
50 static ui_menu_add_f createMenuItem[] = { |
| 49 /* UI_MENU */ add_menu_widget, |
51 /* UI_MENU */ add_menu_widget, |
| 212 |
176 |
| 213 void add_radioitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) { |
177 void add_radioitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) { |
| 214 // TODO |
178 // TODO |
| 215 } |
179 } |
| 216 |
180 |
| 217 /* |
|
| 218 void add_checkitemnv_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) { |
|
| 219 UiCheckItemNV *ci = (UiCheckItemNV*)item; |
|
| 220 GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label); |
|
| 221 gtk_menu_shell_append(GTK_MENU_SHELL(p), widget); |
|
| 222 |
|
| 223 UiVar *var = uic_create_var(obj->ctx, ci->varname, UI_VAR_INTEGER); |
|
| 224 if(var) { |
|
| 225 UiInteger *value = var->value; |
|
| 226 value->obj = widget; |
|
| 227 value->get = ui_checkitem_get; |
|
| 228 value->set = ui_checkitem_set; |
|
| 229 value = 0; |
|
| 230 } else { |
|
| 231 // TODO: error |
|
| 232 } |
|
| 233 } |
|
| 234 */ |
|
| 235 |
|
| 236 static void menuitem_list_remove_binding(void *obj) { |
181 static void menuitem_list_remove_binding(void *obj) { |
| 237 UiActiveMenuItemList *ls = obj; |
182 UiActiveMenuItemList *ls = obj; |
| 238 UiList *list = ls->var->value; |
183 UiList *list = ls->var->value; |
| 239 CxList *bindings = list->obj; |
184 CxList *bindings = list->obj; |
| 240 if(bindings) { |
185 if(bindings) { |
| 479 createMenuItem[it->type](parent, index++, it, obj); |
427 createMenuItem[it->type](parent, index++, it, obj); |
| 480 } |
428 } |
| 481 } |
429 } |
| 482 it = it->next; |
430 it = it->next; |
| 483 } |
431 } |
| |
432 if(section) { |
| |
433 g_object_unref(section); |
| |
434 } |
| 484 } |
435 } |
| 485 |
436 |
| 486 void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) { |
437 void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) { |
| 487 UiMenu *mi = (UiMenu*)item; |
438 UiMenu *mi = (UiMenu*)item; |
| 488 GMenu *menu = g_menu_new(); |
439 GMenu *menu = g_menu_new(); |
| 489 ui_gmenu_add_menu_items(menu, 0, mi, obj); |
440 ui_gmenu_add_menu_items(menu, 0, mi, obj); |
| 490 g_menu_append_submenu(parent, mi->label, G_MENU_MODEL(menu)); |
441 g_menu_append_submenu(parent, mi->label, G_MENU_MODEL(menu)); |
| |
442 g_object_unref(menu); |
| 491 } |
443 } |
| 492 |
444 |
| 493 static void action_enable(GSimpleAction *action, int enabled) { |
445 static void action_enable(GSimpleAction *action, int enabled) { |
| 494 g_simple_action_set_enabled(action, enabled); |
446 g_simple_action_set_enabled(action, enabled); |
| 495 } |
447 } |
| 497 void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) { |
449 void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) { |
| 498 UiMenuItem *i = (UiMenuItem*)item; |
450 UiMenuItem *i = (UiMenuItem*)item; |
| 499 |
451 |
| 500 GSimpleAction *action = g_simple_action_new(item->id, NULL); |
452 GSimpleAction *action = g_simple_action_new(item->id, NULL); |
| 501 g_action_map_add_action(obj->ctx->action_map, G_ACTION(action)); |
453 g_action_map_add_action(obj->ctx->action_map, G_ACTION(action)); |
| |
454 g_object_unref(action); |
| 502 |
455 |
| 503 if(i->groups) { |
456 if(i->groups) { |
| 504 CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); |
457 CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); |
| 505 cxListAddArray(groups, i->groups, i->ngroups); |
458 cxListAddArray(groups, i->groups, i->ngroups); |
| 506 uic_add_group_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups); |
459 uic_add_group_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups); |
| 541 UiMenuCheckItem *checkitem = (UiMenuCheckItem*)item; |
495 UiMenuCheckItem *checkitem = (UiMenuCheckItem*)item; |
| 542 |
496 |
| 543 // TODO |
497 // TODO |
| 544 } |
498 } |
| 545 |
499 |
| 546 void ui_gmenu_add_radioitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) { |
500 |
| 547 |
501 |
| |
502 typedef struct UiCallbackData { |
| |
503 ui_callback callback; |
| |
504 void *userdata; |
| |
505 } UiCallbackData; |
| |
506 |
| |
507 static void radiogroup_remove_binding(void *obj) { |
| |
508 UiMenuRadioGroup *group = obj; |
| |
509 if(group->var) { |
| |
510 UiInteger *i = group->var->value; |
| |
511 CxList *bindings = i->obj; |
| |
512 if(bindings) { |
| |
513 (void)cxListFindRemove(bindings, obj); |
| |
514 } |
| |
515 } |
| |
516 } |
| |
517 |
| |
518 static void stateful_action_notify_group(UiMenuRadioGroup *group, UiInteger *i) { |
| |
519 UiEvent event; |
| |
520 event.obj = group->obj; |
| |
521 event.window = event.obj->window; |
| |
522 event.document = event.obj->ctx->document; |
| |
523 event.eventdata = NULL; |
| |
524 event.eventdatatype = 0; |
| |
525 event.intval = (int)i->value; |
| |
526 event.set = ui_get_setop(); |
| |
527 |
| |
528 CxIterator iter = cxListIterator(group->callbacks); |
| |
529 cx_foreach(UiCallbackData *, cb, iter) { |
| |
530 if(cb->callback) { |
| |
531 cb->callback(&event, cb->userdata); |
| |
532 } |
| |
533 } |
| |
534 |
| |
535 UiObserver *obs = i->observers; |
| |
536 while(obs) { |
| |
537 if(obs->callback) { |
| |
538 obs->callback(&event, obs->data); |
| |
539 } |
| |
540 obs = obs->next; |
| |
541 } |
| |
542 } |
| |
543 |
| |
544 static void ui_action_set_state(UiInteger *i, int64_t value) { |
| |
545 i->value = value; |
| |
546 CxList *bindings = i->obj; |
| |
547 CxIterator iter = cxListIterator(bindings); |
| |
548 cx_foreach(UiMenuRadioGroup *, group, iter) { |
| |
549 char buf[32]; |
| |
550 snprintf(buf, 32, "%d", (int)value); |
| |
551 GVariant *state = g_variant_new_string(buf); |
| |
552 g_action_change_state(G_ACTION(group->action), state); |
| |
553 stateful_action_notify_group(group, i); |
| |
554 } |
| |
555 } |
| |
556 |
| |
557 static int64_t ui_action_get_state(UiInteger *i) { |
| |
558 return i->value; |
| |
559 } |
| |
560 |
| |
561 static UiMenuRadioGroup* create_radio_group(UiObject *obj, UiMenuRadioItem *item, GSimpleAction *action) { |
| |
562 UiMenuRadioGroup *group = cxZalloc(obj->ctx->allocator, sizeof(UiMenuRadioGroup)); |
| |
563 group->callbacks = cxArrayListCreate(obj->ctx->allocator, NULL, sizeof(UiCallbackData), 8); |
| |
564 group->var = uic_create_var(ui_global_context(), item->varname, UI_VAR_INTEGER); |
| |
565 group->obj = obj; |
| |
566 group->action = action; |
| |
567 if(group->var) { |
| |
568 UiInteger *i = group->var->value; |
| |
569 CxList *bindings = i->obj; |
| |
570 if(!bindings) { |
| |
571 bindings = cxLinkedListCreate(group->var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS); |
| |
572 i->obj = bindings; |
| |
573 i->set = ui_action_set_state; |
| |
574 i->get = ui_action_get_state; |
| |
575 } |
| |
576 cxListAdd(bindings, group); |
| |
577 // the destruction of the toplevel obj must remove the binding |
| |
578 uic_context_add_destructor(obj->ctx, radiogroup_remove_binding, group); |
| |
579 } |
| |
580 return group; |
| |
581 } |
| |
582 |
| |
583 static void stateful_action_activate( |
| |
584 GSimpleAction *action, |
| |
585 GVariant *state, |
| |
586 UiMenuRadioGroup *group) |
| |
587 { |
| |
588 g_simple_action_set_state(action, state); |
| |
589 |
| |
590 UiVar *var = group->var; |
| |
591 if(!var) { |
| |
592 return; |
| |
593 } |
| |
594 UiInteger *i = var->value; |
| |
595 |
| |
596 gsize len; |
| |
597 const char *s = g_variant_get_string(state, &len); |
| |
598 cxstring value = cx_strn(s, len); |
| |
599 int v; |
| |
600 if(!cx_strtoi(value, &v, 10)) { |
| |
601 i->value = v; |
| |
602 } |
| |
603 |
| |
604 CxList *bindings = i->obj; |
| |
605 CxIterator iter = cxListIterator(bindings); |
| |
606 cx_foreach(UiMenuRadioGroup *, gr, iter) { |
| |
607 stateful_action_notify_group(gr, i); |
| |
608 } |
| |
609 } |
| |
610 |
| |
611 |
| |
612 void ui_gmenu_add_radioitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) { |
| |
613 UiMenuRadioItem *i = (UiMenuRadioItem*)item; |
| |
614 |
| |
615 if(!i->varname) { |
| |
616 return; |
| |
617 } |
| |
618 |
| |
619 // All radio buttons with the name varname use the same GAction |
| |
620 UiMenuRadioGroup *group = NULL; |
| |
621 GAction *action = g_action_map_lookup_action(obj->ctx->action_map, i->varname); |
| |
622 if(!action) { |
| |
623 GVariant *state = g_variant_new_string("0"); |
| |
624 GSimpleAction *newAction = g_simple_action_new_stateful(i->varname, G_VARIANT_TYPE_STRING, state); |
| |
625 g_action_map_add_action(obj->ctx->action_map, G_ACTION(newAction)); |
| |
626 g_object_unref(newAction); |
| |
627 |
| |
628 group = create_radio_group(obj, i, newAction); |
| |
629 g_object_set_data(G_OBJECT(newAction), "ui_radiogroup", group); |
| |
630 |
| |
631 g_signal_connect( |
| |
632 newAction, |
| |
633 "change-state", |
| |
634 G_CALLBACK(stateful_action_activate), |
| |
635 group); |
| |
636 } else { |
| |
637 group = g_object_get_data(G_OBJECT(action), "ui_radiogroup"); |
| |
638 if(!group) { |
| |
639 fprintf(stderr, "Error: missing ui_radiogroup property for action %s\n", i->varname); |
| |
640 return; // error, should not happen |
| |
641 } |
| |
642 } |
| |
643 |
| |
644 size_t item_index = cxListSize(group->callbacks); |
| |
645 |
| |
646 UiCallbackData cb; |
| |
647 cb.callback = i->callback; |
| |
648 cb.userdata = i->userdata; |
| |
649 cxListAdd(group->callbacks, &cb); |
| |
650 |
| |
651 |
| |
652 cxmutstr action_name = cx_asprintf("win.%s::%d", i->varname, (int)item_index); |
| |
653 g_menu_append(parent, i->label, action_name.ptr); |
| |
654 free(action_name.ptr); |
| 548 } |
655 } |
| 549 |
656 |
| 550 static void menuitem_list_remove_binding(void *obj) { |
657 static void menuitem_list_remove_binding(void *obj) { |
| 551 UiActiveGMenuItemList *ls = obj; |
658 UiActiveGMenuItemList *ls = obj; |
| 552 UiList *list = ls->var->value; |
659 UiList *list = ls->var->value; |
| 708 |
817 |
| 709 UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, GtkWidget *widget) { |
818 UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, GtkWidget *widget) { |
| 710 GMenu *menu = g_menu_new(); |
819 GMenu *menu = g_menu_new(); |
| 711 ui_gmenu_add_menu_items(menu, 0, builder->menus_begin, obj); |
820 ui_gmenu_add_menu_items(menu, 0, builder->menus_begin, obj); |
| 712 GtkWidget *contextmenu = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu)); |
821 GtkWidget *contextmenu = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu)); |
| |
822 g_object_unref(menu); |
| 713 gtk_popover_set_has_arrow(GTK_POPOVER(contextmenu), FALSE); |
823 gtk_popover_set_has_arrow(GTK_POPOVER(contextmenu), FALSE); |
| 714 gtk_widget_set_halign(contextmenu, GTK_ALIGN_START); |
824 gtk_widget_set_halign(contextmenu, GTK_ALIGN_START); |
| 715 gtk_widget_set_parent(GTK_WIDGET(contextmenu), widget); |
825 gtk_widget_set_parent(GTK_WIDGET(contextmenu), widget); |
| 716 g_signal_connect( |
826 g_signal_connect( |
| 717 widget, |
827 widget, |