Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stateful action with parameter #50

Closed
hja3 opened this issue Jan 26, 2024 · 2 comments · Fixed by #51
Closed

Add stateful action with parameter #50

hja3 opened this issue Jan 26, 2024 · 2 comments · Fixed by #51

Comments

@hja3
Copy link

hja3 commented Jan 26, 2024

Hello.

I couldn't find a way to add a stateful action with parameter to GActionMap out of the box.

The following does the trick getting the radio menu items to work, but requires a custom overload of add_stateful_action.

Perhaps there is something I have overlooked?

using Gtk4, Gtk4.GLib

function Gtk4.add_stateful_action(m::GActionMap, name::AbstractString,
                             initial_state, handler::Function,
                             parameter::Bool = false)
    pvtype = parameter ? GVariantType(typeof(initial_state)) : nothing
    action = GSimpleAction(name, pvtype, GVariant(initial_state))
    push!(m, GAction(action))
    signal_connect(handler, action, :change_state)
    action
end

const menu_xml = """
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.16.0 -->
<interface>
  <!-- interface-name menus.ui -->
  <requires lib="gio" version="2.0"/>
  <menu id="menubar">
    <submenu>
      <attribute name="label">Menu</attribute>
      <item>
        <attribute name="action">app.check</attribute>
        <attribute name="label">Check</attribute>
      </item>
      <item>
        <attribute name="action">app.radio</attribute>
        <attribute name="label">Radio 1</attribute>
        <attribute name="target">1</attribute>
      </item>
      <item>
        <attribute name="action">app.radio</attribute>
        <attribute name="label">Radio 2</attribute>
        <attribute name="target">2</attribute>
      </item>
    </submenu>
  </menu>
</interface>
"""

function on_check_changed(a, v) ::Nothing
    println("check changed: ", v[Bool])
    Gtk4.GLib.set_state(a, v)
end

function on_radio_changed(a, v) ::Nothing
    println("radio changed: ", v[String])
    Gtk4.GLib.set_state(a, v)
end

function activate(app)
    # main window
    window = GtkApplicationWindow(app)
    add_action(GActionMap(window), "close", _ -> destroy(window))

    # build menu
    add_stateful_action(GActionMap(app), "check", true, on_check_changed)
    add_stateful_action(GActionMap(app), "radio", "1", on_radio_changed, true)
    builder = GtkBuilder(menu_xml, length(menu_xml))
    Gtk4.G_.set_menubar(app, builder["menubar"])
    window.show_menubar = true

    show(window)
end

function julia_main() ::Cint
    app = GtkApplication("julia.menubar.example")
    Gtk4.signal_connect(activate, app, :activate)

    # When all windows are closed, loop automatically stops running
    Gtk4.run(app)

    return 0
end
@jwahlstrand
Copy link
Member

You didn't overlook anything, that was just not implemented! I will add your suggested method, unless you want to create a PR?

@hja3
Copy link
Author

hja3 commented Jan 27, 2024

I am not experienced with the GitHub workflow, sorry. I would be grateful if you could add it.

The version I eventually settled on here refactors both stateless and stateful actions to handle parameters, with the non-parameter method forwarding to the parameter method in each case. Please feel free to use this code if you think it is a good approach:

using Gtk4, Gtk4.GLib
import Gtk4: add_action, add_stateful_action

# stateless, with parameter
function add_action(m::GActionMap, name::AbstractString,
                    parameter::Type{T}, handler::Function) where T
    pvtype = parameter == Nothing ? nothing : GVariantType(parameter)
    action = GSimpleAction(name, pvtype)
    push!(m, GAction(action))
    signal_connect(handler, action, :activate)
    action
end

# stateless, without parameter
function add_action(m::GActionMap, name::AbstractString, handler::Function)
    add_action(m, name, Nothing, handler)
end

# stateful, with parameter
function add_stateful_action(m::GActionMap, name::AbstractString,
                             parameter::Type{T}, initial_state,
                             handler::Function) where T
    pvtype = parameter == Nothing ? nothing : GVariantType(parameter)
    action = GSimpleAction(name, pvtype, GVariant(initial_state))
    push!(m, GAction(action))
    signal_connect(handler, action, :change_state)
    action
end

# stateful, without parameter
function add_stateful_action(m::GActionMap, name::AbstractString,
                             initial_state, handler::Function)
    add_stateful_action(m, name, Nothing, initial_state, handler)
end

const menu_xml = """
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.16.0 -->
<interface>
  <!-- interface-name menus.ui -->
  <requires lib="gio" version="2.0"/>
  <menu id="menubar">
    <submenu>
      <attribute name="label">Menu</attribute>
      <item>
        <attribute name="action">app.action</attribute>
        <attribute name="label">Action</attribute>
      </item>
      <item>
        <attribute name="action">app.action-param</attribute>
        <attribute name="label">Foo</attribute>
        <attribute name="target">foo</attribute>
      </item>
      <item>
        <attribute name="action">app.action-param</attribute>
        <attribute name="label">Bar</attribute>
        <attribute name="target">bar</attribute>
      </item>
      <item>
        <attribute name="action">app.check</attribute>
        <attribute name="label">Check</attribute>
      </item>
      <item>
        <attribute name="action">app.radio</attribute>
        <attribute name="label">Radio 1</attribute>
        <attribute name="target" type="x">1</attribute>
      </item>
      <item>
        <attribute name="action">app.radio</attribute>
        <attribute name="label">Radio 2</attribute>
        <attribute name="target" type="x">2</attribute>
      </item>
    </submenu>
  </menu>
</interface>
"""

function on_action(a, v) ::Nothing
    println("action")
end

function on_action_param(a, v) ::Nothing
    println("action, param: ", v[String])
end

function on_check_changed(a, v) ::Nothing
    println("check changed: ", v[Bool])
    Gtk4.GLib.set_state(a, v)
end

function on_radio_changed(a, v) ::Nothing
    println("radio changed: ", v[Int])
    Gtk4.GLib.set_state(a, v)
end

function activate(app)
    # main window
    window = GtkApplicationWindow(app)

    # build menu
    add_action(GActionMap(app), "action", on_action)
    add_action(GActionMap(app), "action-param", String, on_action_param)
    add_stateful_action(GActionMap(app), "check", true, on_check_changed)
    add_stateful_action(GActionMap(app), "radio", Int, 1, on_radio_changed)
    builder = GtkBuilder(menu_xml, length(menu_xml))
    Gtk4.G_.set_menubar(app, builder["menubar"])
    window.show_menubar = true

    show(window)
end

function julia_main() ::Cint
    app = GtkApplication("julia.menubar.example")
    Gtk4.signal_connect(activate, app, :activate)

    # When all windows are closed, loop automatically stops running
    Gtk4.run(app)

    return 0
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants