package io.gitlab.jfronny.inceptum.gtk.menu; import ch.bailu.gtk.GTK; import ch.bailu.gtk.gio.MenuItem; import ch.bailu.gtk.gio.*; import ch.bailu.gtk.glib.Variant; import ch.bailu.gtk.glib.VariantType; import ch.bailu.gtk.gtk.Application; import ch.bailu.gtk.gtk.PopoverMenu; import ch.bailu.gtk.type.Pointer; import ch.bailu.gtk.type.Str; import io.gitlab.jfronny.commons.throwable.ThrowingRunnable; import io.gitlab.jfronny.inceptum.common.Utils; import io.gitlab.jfronny.inceptum.gtk.util.I18n; import java.util.*; import java.util.function.*; public class MenuBuilder { private static final Object LOCK = new Object(); private static Menu getRootMenu(Application app) { synchronized (LOCK) { var currentMenu = app.getMenubar(); if (currentMenu.equals(Pointer.NULL)) { var menu = new Menu(); app.setMenubar(menu); return menu; } else { return new Menu(currentMenu.cast()); } } } private final ActionMap map; private final Map refs = new LinkedHashMap<>(); private final Menu menu; private final String prefix; public MenuBuilder(Application app) { this(app, getRootMenu(app), ""); } public MenuBuilder(Application app, Menu menu, String prefix) { this(new ActionMap(app.cast()), menu, prefix); } public MenuBuilder(ActionMap map, Menu menu, String prefix) { if (!prefix.isEmpty() && !prefix.endsWith(".")) prefix += "."; this.map = Objects.requireNonNull(map); this.menu = Objects.requireNonNull(menu); this.prefix = Objects.requireNonNull(prefix); } public ButtonItem button(String name, ThrowingRunnable onClick) { return literalButton(name, I18n.get("menu." + prefix + name), onClick); } public ButtonItem literalButton(String internalName, String label, ThrowingRunnable onClick) { internalName = prefix + internalName; SimpleAction sAct = new SimpleAction(new Str(internalName), null); addAction(internalName, sAct); sAct.onActivate(v -> { try { onClick.run(); } catch (Throwable e) { Utils.LOGGER.error("Could not execute action", e); } }); menu.appendItem(new MenuItem(new Str(label), new Str("app." + internalName))); return new ButtonItem(sAct); } public ToggleItem toggle(String name, boolean initial, Consumer action) { name = prefix + name; SimpleAction sAct = SimpleAction.newStatefulSimpleAction(new Str(name), null, Variant.newBooleanVariant(GTK.is(initial))); Action rAct = addAction(name, sAct); sAct.onActivate(parameter -> { boolean state = !GTK.is(rAct.getState().getBoolean()); sAct.setState(Variant.newBooleanVariant(GTK.is(state))); action.accept(state); }); menu.appendItem(new MenuItem(I18n.str("menu." + name), new Str("app." + name))); return new ToggleItem(sAct); } public RadioItem radio(String name, T initial, List options, Consumer action) { return literalRadio(name, initial, options, (i, t) -> I18n.get("menu." + name, i), action); } public RadioItem literalRadio(String name, T initial, List options, BiFunction stringifier, Consumer action) { Objects.requireNonNull(options); name = prefix + name; SimpleAction sAct = SimpleAction.newStatefulSimpleAction(new Str(name), new VariantType(new Str("i")), Variant.newInt32Variant(options.indexOf(initial))); addAction(name, sAct); sAct.onActivate(parameter -> { sAct.setState(parameter); action.accept(options.get(parameter.getInt32())); }); int i = 0; for (T option : options) { menu.appendItem(new MenuItem(new Str(stringifier.apply(i, option)), new Str("app." + name + "(" + i + ")"))); i++; } return new RadioItem<>(sAct, options); } public MenuBuilder submenu(String name) { name = prefix + name; Menu submenu = new Menu(); menu.appendSubmenu(I18n.str("menu." + name), submenu); return new MenuBuilder(map, submenu, name); } public void clear() { menu.removeAll(); refs.forEach((name, action) -> { map.removeAction(new Str(name)); }); refs.clear(); } private Action addAction(String name, SimpleAction action) { Action rAct = new Action(action.cast()); map.addAction(rAct); refs.put(name, rAct); return rAct; } public Menu getMenu() { return menu; } public PopoverMenu asPopover() { return PopoverMenu.newFromModelPopoverMenu(menu); } }