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  }