github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/linux/menu.go (about) 1 //go:build linux 2 // +build linux 3 4 package linux 5 6 /* 7 #cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 8 9 #include "gtk/gtk.h" 10 11 static GtkMenuItem *toGtkMenuItem(void *pointer) { return (GTK_MENU_ITEM(pointer)); } 12 static GtkMenuShell *toGtkMenuShell(void *pointer) { return (GTK_MENU_SHELL(pointer)); } 13 static GtkCheckMenuItem *toGtkCheckMenuItem(void *pointer) { return (GTK_CHECK_MENU_ITEM(pointer)); } 14 static GtkRadioMenuItem *toGtkRadioMenuItem(void *pointer) { return (GTK_RADIO_MENU_ITEM(pointer)); } 15 16 extern void handleMenuItemClick(void*); 17 18 void blockClick(GtkWidget* menuItem, gulong handler_id) { 19 g_signal_handler_block (menuItem, handler_id); 20 } 21 22 void unblockClick(GtkWidget* menuItem, gulong handler_id) { 23 g_signal_handler_unblock (menuItem, handler_id); 24 } 25 26 gulong connectClick(GtkWidget* menuItem) { 27 return g_signal_connect(menuItem, "activate", G_CALLBACK(handleMenuItemClick), (void*)menuItem); 28 } 29 30 void addAccelerator(GtkWidget* menuItem, GtkAccelGroup* group, guint key, GdkModifierType mods) { 31 gtk_widget_add_accelerator(menuItem, "activate", group, key, mods, GTK_ACCEL_VISIBLE); 32 } 33 */ 34 import "C" 35 import "github.com/secoba/wails/v2/pkg/menu" 36 import "unsafe" 37 38 var menuIdCounter int 39 var menuItemToId map[*menu.MenuItem]int 40 var menuIdToItem map[int]*menu.MenuItem 41 var gtkCheckboxCache map[*menu.MenuItem][]*C.GtkWidget 42 var gtkMenuCache map[*menu.MenuItem]*C.GtkWidget 43 var gtkRadioMenuCache map[*menu.MenuItem][]*C.GtkWidget 44 var gtkSignalHandlers map[*C.GtkWidget]C.gulong 45 var gtkSignalToMenuItem map[*C.GtkWidget]*menu.MenuItem 46 47 func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) { 48 f.mainWindow.SetApplicationMenu(menu) 49 } 50 51 func (f *Frontend) MenuUpdateApplicationMenu() { 52 f.mainWindow.SetApplicationMenu(f.mainWindow.applicationMenu) 53 } 54 55 func (w *Window) SetApplicationMenu(inmenu *menu.Menu) { 56 if inmenu == nil { 57 return 58 } 59 60 // Setup accelerator group 61 w.accels = C.gtk_accel_group_new() 62 C.gtk_window_add_accel_group(w.asGTKWindow(), w.accels) 63 64 menuItemToId = make(map[*menu.MenuItem]int) 65 menuIdToItem = make(map[int]*menu.MenuItem) 66 gtkCheckboxCache = make(map[*menu.MenuItem][]*C.GtkWidget) 67 gtkMenuCache = make(map[*menu.MenuItem]*C.GtkWidget) 68 gtkRadioMenuCache = make(map[*menu.MenuItem][]*C.GtkWidget) 69 gtkSignalHandlers = make(map[*C.GtkWidget]C.gulong) 70 gtkSignalToMenuItem = make(map[*C.GtkWidget]*menu.MenuItem) 71 72 // Increase ref count? 73 w.menubar = C.gtk_menu_bar_new() 74 75 processMenu(w, inmenu) 76 77 C.gtk_widget_show(w.menubar) 78 } 79 80 func processMenu(window *Window, menu *menu.Menu) { 81 for _, menuItem := range menu.Items { 82 submenu := processSubmenu(menuItem, window.accels) 83 C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(window.menubar)), submenu) 84 } 85 } 86 87 func processSubmenu(menuItem *menu.MenuItem, group *C.GtkAccelGroup) *C.GtkWidget { 88 existingMenu := gtkMenuCache[menuItem] 89 if existingMenu != nil { 90 return existingMenu 91 } 92 gtkMenu := C.gtk_menu_new() 93 submenu := GtkMenuItemWithLabel(menuItem.Label) 94 for _, menuItem := range menuItem.SubMenu.Items { 95 menuID := menuIdCounter 96 menuIdToItem[menuID] = menuItem 97 menuItemToId[menuItem] = menuID 98 menuIdCounter++ 99 processMenuItem(gtkMenu, menuItem, group) 100 } 101 C.gtk_menu_item_set_submenu(C.toGtkMenuItem(unsafe.Pointer(submenu)), gtkMenu) 102 gtkMenuCache[menuItem] = existingMenu 103 return submenu 104 } 105 106 var currentRadioGroup *C.GSList 107 108 func processMenuItem(parent *C.GtkWidget, menuItem *menu.MenuItem, group *C.GtkAccelGroup) { 109 if menuItem.Hidden { 110 return 111 } 112 113 if menuItem.Type != menu.RadioType { 114 currentRadioGroup = nil 115 } 116 117 if menuItem.Type == menu.SeparatorType { 118 result := C.gtk_separator_menu_item_new() 119 C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(parent)), result) 120 return 121 } 122 123 var result *C.GtkWidget 124 125 switch menuItem.Type { 126 case menu.TextType: 127 result = GtkMenuItemWithLabel(menuItem.Label) 128 case menu.CheckboxType: 129 result = GtkCheckMenuItemWithLabel(menuItem.Label) 130 if menuItem.Checked { 131 C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(result)), 1) 132 } 133 gtkCheckboxCache[menuItem] = append(gtkCheckboxCache[menuItem], result) 134 135 case menu.RadioType: 136 result = GtkRadioMenuItemWithLabel(menuItem.Label, currentRadioGroup) 137 currentRadioGroup = C.gtk_radio_menu_item_get_group(C.toGtkRadioMenuItem(unsafe.Pointer(result))) 138 if menuItem.Checked { 139 C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(result)), 1) 140 } 141 gtkRadioMenuCache[menuItem] = append(gtkRadioMenuCache[menuItem], result) 142 case menu.SubmenuType: 143 result = processSubmenu(menuItem, group) 144 } 145 C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(parent)), result) 146 C.gtk_widget_show(result) 147 148 if menuItem.Click != nil { 149 handler := C.connectClick(result) 150 gtkSignalHandlers[result] = handler 151 gtkSignalToMenuItem[result] = menuItem 152 } 153 154 if menuItem.Disabled { 155 C.gtk_widget_set_sensitive(result, 0) 156 } 157 158 if menuItem.Accelerator != nil { 159 key, mods := acceleratorToGTK(menuItem.Accelerator) 160 C.addAccelerator(result, group, key, mods) 161 } 162 }