package io.gitlab.jfronny.inceptum.gtk.menu; import io.gitlab.jfronny.commons.throwable.ThrowingRunnable; import io.gitlab.jfronny.inceptum.common.Utils; import io.gitlab.jfronny.inceptum.gtk.util.I18n; import org.gtk.gio.MenuItem; import org.gtk.gio.*; import org.gtk.glib.Variant; import org.gtk.glib.VariantType; import org.gtk.gtk.Application; import org.gtk.gtk.PopoverMenu; import org.jetbrains.annotations.Nullable; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Consumer; public class MenuBuilder { private static final Object LOCK = new Object(); private static Menu getRootMenu(Application app) { synchronized (LOCK) { var currentMenu = app.menubar; if (currentMenu == null) { var menu = new Menu(); app.menubar = menu; return menu; } else { return (Menu) currentMenu; } } } 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(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 BuiltButtonItem button(String name, ThrowingRunnable onClick) { return literalButton(name, I18n.get("menu." + prefix + name), onClick); } public BuiltButtonItem literalButton(String internalName, String label, ThrowingRunnable onClick) { internalName = prefix + internalName; SimpleAction action = new SimpleAction(internalName, null); addAction(internalName, action); action.onActivate(variant -> { try { onClick.run(); } catch (Throwable e) { Utils.LOGGER.error("Could not execute action", e); } }); MenuItem menuItem = new MenuItem(label, "app." + internalName); menu.appendItem(menuItem); action.enabled = true; return new BuiltButtonItem(action, menuItem); } public BuiltToggleItem toggle(String name, boolean initial, Consumer onToggle) { name = prefix + name; SimpleAction action = SimpleAction.newStateful(name, null, Variant.newBoolean(initial)); addAction(name, action); action.onActivate(variant -> { boolean state = !action.getState().getBoolean(); action.state = Variant.newBoolean(state); onToggle.accept(state); }); MenuItem menuItem = new MenuItem(I18n.get("menu." + name), "app." + name); menu.appendItem(menuItem); return new BuiltToggleItem(action, menuItem); } public BuiltRadioItem radio(String name, T initial, List options, Consumer onCheck) { return literalRadio(name, initial, options, (i, t) -> I18n.get("menu." + prefix + name, i), onCheck); } public BuiltRadioItem literalRadio(String name, T initial, List options, BiFunction stringifier, Consumer onCheck) { Objects.requireNonNull(options); name = prefix + name; SimpleAction action = SimpleAction.newStateful(name, new VariantType("i"), Variant.newInt32(options.indexOf(initial))); addAction(name, action); action.onActivate(variant -> { action.state = variant; onCheck.accept(options.get(variant.getInt32())); }); int i = 0; for (T option : options) { menu.appendItem(new MenuItem(stringifier.apply(i, option), "app." + name + "(" + i + ")")); i++; } return new BuiltRadioItem<>(action, options); } public MenuBuilder submenu(String name) { return literalSubmenu(name, I18n.get("menu." + prefix + name)); } public MenuBuilder literalSubmenu(String name, String label) { name = prefix + name; Menu submenu = new Menu(); menu.appendSubmenu(label, submenu); return new MenuBuilder(map, submenu, name); } public MenuBuilder section(String name) { return literalSection(name, I18n.get("section." + prefix + name)); } public MenuBuilder literalSection(String name, @Nullable String label) { name = prefix + name; Menu section = new Menu(); menu.appendSection(label, section); return new MenuBuilder(map, section, name); } public void clear() { menu.removeAll(); refs.forEach((name, action) -> { map.removeAction(name); }); refs.clear(); } private void addAction(String name, SimpleAction action) { map.addAction(action); refs.put(name, action); } public Menu getMenu() { return menu; } public PopoverMenu asPopover() { return PopoverMenu.newFromModel(menu); } }