diff --git a/README.adoc b/README.adoc index cbacc7a..ae7c58a 100644 --- a/README.adoc +++ b/README.adoc @@ -811,6 +811,131 @@ these conversions are rare, and because gintro use no converters at all still, w an explicit proc.) The use of cstringArray as third parameter for proc setAccelsForAction() is a bit ugly, we have to fix that later. + + +== GMenu with GActions + +The following example shows how we can define GActions and bind them to Menus, Buttons +and Keyboard shortcuts. Examples for stateless actions (quit), for toggle actions (spellcheck) +and for statefull actions (text justify) are provided. + +Note that the following code is not a direct translation of an existing example, but +a collections of informations from various sources, so +it may contain bugs or not fully optimal code. + +[[menubar.nim]] +[source, nim] +.menubar.nim +---- +# https://developer.gnome.org/glib/stable/glib-GVariant.html +# https://developer.gnome.org/glib/stable/glib-GVariantType.html +# https://wiki.gnome.org/HowDoI/GMenu +# https://wiki.gnome.org/HowDoI/GAction +# nim c menubar.nim +import gintro/[gtk, glib, gobject, gio] +from strutils import `%`, format + +# https://github.com/GNOME/glib/blob/master/gio/tests/gapplication-example-actions.c +proc activateToggleAction(action: SimpleAction; parameter: Variant; app: Application) = + app.hold # hold/release taken over from C example, there may be reasons... + block: + echo format("action $1 activated", action.name) + let state: Variant = action.state + let b = state.getBoolean + action.state = newVariantBoolean(not b) + echo format("state change $1 -> $2", b, not b) + app.release + +proc activateStatefulAction(action: SimpleAction; parameter: Variant; app: Application) = + app.hold + block: + echo format("action $1 activated", action.name) + let state: Variant = action.state + var l: uint64 + let oldState = state.getString(l) # yes uint64 parameter is a bit ugly + let newState = parameter.getString(l) + action.state = newVariantString(newState) + echo format("state change $1 -> $2", oldState, newState) + app.release + +proc quitProgram(action: SimpleAction; parameter: Variant; app: Application) = + quit(app) + +proc appStartup(app: Application) = + let quit = newSimpleAction("quit") # here we create the actions for whole app + connect(quit, "activate", quitProgram, app) + app.addAction(quit) + + let menu = gio.newMenu() # root of all menus + block: # plain stateless menu + let subMenu = gio.newMenu() + menu.appendSubMenu("Application", submenu) + # let section = gio.newMenu() # no separating section needed here + # submenu.appendSection(nil, section) + # section.append("Quit", "app.quit") + submenu.append("Quit", "app.quit") + + block: #stateful menu with radio items + let subMenu = gio.newMenu() + menu.appendSubMenu("Layout", submenu) + let subMenu2 = gio.newMenu() + submenu.appendSubMenu("justify", submenu2) + let section = gio.newMenu() + submenu2.appendSection(nil, section) + section.append("left", "win.justify::left") + section.append("center", "win.justify::center") + section.append("right", "win.justify::right") + + block: # and finally a toggle menu + let subMenu = gio.newMenu() + menu.appendSubMenu("Spelling", submenu) + let section = gio.newMenu() + submenu.appendSection(nil, section) + section.append("Check", "win.toggleSpellCheck") + # finally add the menubar + setMenuBar(app, menu) + +proc appActivate(app: Application) = + let window = newApplicationWindow(app) + window.title = "GTK3 App with Menubar" + window.defaultSize = (500, 200) + window.position = WindowPosition.center + block: # creat the window related actions + let v = newVariantBoolean(true) + let spellCheck = newSimpleActionStateful("toggleSpellCheck", nil, v) + connect(spellCheck, "activate", activateToggleAction, app) + window.actionMap.addAction(spellCheck) + block: + let v = newVariantString("left") # default value and + let vt = newVariantType("s") # string (value type) + let justifyAction = newSimpleActionStateful("justify", vt, v) + connect(justifyAction, "activate", activateStatefulAction, app) + window.actionMap.addAction(justifyAction) + let button = newButton() + button.label = "Justify Center" + #window.add(button) # do not add it here already: (menubar:10010): Gtk-WARNING **: + # 22:00:33.230: actionhelper: action win.justify can't be activated due to + # parameter type mismatch (parameter type s, target type NULL) + button.setDetailedActionName("win.justify::center") + #button.setActionName("app.quit") # for a stateless action + var h: cstringArray = allocCstringArray(["R"]) # ugly + setAccelsForAction(app, "win.justify::right", h) + deallocCStringArray(h) + window.add(button) + showAll(window) + +proc main = + let app = newApplication("app.example") + connect(app, "startup", appStartup) + connect(app, "activate", appActivate) + echo "GTK Version $1.$2.$3" % [$majorVersion(), $minorVersion(), $microVersion()] + let status = run(app) + quit(status) + +main() + +---- + == Drawing with Cairo graphics library The next example shows how we can use the cairo graphics library for drawing on a DrawingArea widget, diff --git a/examples/menubar.nim b/examples/menubar.nim new file mode 100644 index 0000000..03bbeeb --- /dev/null +++ b/examples/menubar.nim @@ -0,0 +1,105 @@ +# https://developer.gnome.org/glib/stable/glib-GVariant.html +# https://developer.gnome.org/glib/stable/glib-GVariantType.html +# https://wiki.gnome.org/HowDoI/GMenu +# https://wiki.gnome.org/HowDoI/GAction +# nim c menubar.nim +import gintro/[gtk, glib, gobject, gio] +from strutils import `%`, format + +# https://github.com/GNOME/glib/blob/master/gio/tests/gapplication-example-actions.c +proc activateToggleAction(action: SimpleAction; parameter: Variant; app: Application) = + app.hold # hold/release taken over from C example, there may be reasons... + block: + echo format("action $1 activated", action.name) + let state: Variant = action.state + let b = state.getBoolean + action.state = newVariantBoolean(not b) + echo format("state change $1 -> $2", b, not b) + app.release + +proc activateStatefulAction(action: SimpleAction; parameter: Variant; app: Application) = + app.hold + block: + echo format("action $1 activated", action.name) + let state: Variant = action.state + var l: uint64 + let oldState = state.getString(l) # yes uint64 parameter is a bit ugly + let newState = parameter.getString(l) + action.state = newVariantString(newState) + echo format("state change $1 -> $2", oldState, newState) + app.release + +proc quitProgram(action: SimpleAction; parameter: Variant; app: Application) = + quit(app) + +proc appStartup(app: Application) = + let quit = newSimpleAction("quit") # here we create the actions for whole app + connect(quit, "activate", quitProgram, app) + app.addAction(quit) + + let menu = gio.newMenu() # root of all menus + block: # plain stateless menu + let subMenu = gio.newMenu() + menu.appendSubMenu("Application", submenu) + # let section = gio.newMenu() # no separating section needed here + # submenu.appendSection(nil, section) + # section.append("Quit", "app.quit") + submenu.append("Quit", "app.quit") + + block: #stateful menu with radio items + let subMenu = gio.newMenu() + menu.appendSubMenu("Layout", submenu) + let subMenu2 = gio.newMenu() + submenu.appendSubMenu("justify", submenu2) + let section = gio.newMenu() + submenu2.appendSection(nil, section) + section.append("left", "win.justify::left") + section.append("center", "win.justify::center") + section.append("right", "win.justify::right") + + block: # and finally a toggle menu + let subMenu = gio.newMenu() + menu.appendSubMenu("Spelling", submenu) + let section = gio.newMenu() + submenu.appendSection(nil, section) + section.append("Check", "win.toggleSpellCheck") + # finally add the menubar + setMenuBar(app, menu) + +proc appActivate(app: Application) = + let window = newApplicationWindow(app) + window.title = "GTK3 App with Menubar" + window.defaultSize = (500, 200) + window.position = WindowPosition.center + block: # creat the window related actions + let v = newVariantBoolean(true) + let spellCheck = newSimpleActionStateful("toggleSpellCheck", nil, v) + connect(spellCheck, "activate", activateToggleAction, app) + window.actionMap.addAction(spellCheck) + block: + let v = newVariantString("left") # default value and + let vt = newVariantType("s") # string (value type) + let justifyAction = newSimpleActionStateful("justify", vt, v) + connect(justifyAction, "activate", activateStatefulAction, app) + window.actionMap.addAction(justifyAction) + let button = newButton() + button.label = "Justify Center" + #window.add(button) # do not add it here already: (menubar:10010): Gtk-WARNING **: 22:00:33.230: actionhelper: + # action win.justify can't be activated due to parameter type mismatch (parameter type s, target type NULL) + button.setDetailedActionName("win.justify::center") + #button.setActionName("app.quit") # for a stateless action + var h: cstringArray = allocCstringArray(["R"]) # ugly + setAccelsForAction(app, "win.justify::right", h) + deallocCStringArray(h) + window.add(button) + showAll(window) + +proc main = + let app = newApplication("app.example") + connect(app, "startup", appStartup) + connect(app, "activate", appActivate) + echo "GTK Version $1.$2.$3" % [$majorVersion(), $minorVersion(), $microVersion()] + let status = run(app) + quit(status) + +main()