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.gnome.gio.* import org.gnome.glib.Variant import org.gnome.glib.VariantType import org.gnome.gtk.Application import org.gnome.gtk.MenuButton import org.gnome.gtk.PopoverMenu import java.util.function.BiFunction import java.util.function.Consumer class MenuBuilder private constructor(map: ActionMap, menu: Menu, prefix: String, groupName: String) { private val map: ActionMap private val refs: MutableMap = LinkedHashMap() val menu: Menu private val prefix: String private val groupName: String constructor(menu: PopoverMenu, groupName: String) : this( insertMap(menu, groupName), menu.menuModel as Menu, "", groupName ) @JvmOverloads constructor(app: Application, menu: Menu = getRootMenu(app), prefix: String = "") : this(app, menu, prefix, "app.") init { fun String.suffix() = if (isNotEmpty() && !endsWith(".")) "$this." else this this.map = map this.menu = menu this.prefix = prefix.suffix() this.groupName = groupName.suffix() } fun button(name: String, onClick: ThrowingRunnable<*>): BuiltButtonItem { return literalButton(name, I18n["menu.$prefix$name"], onClick) } fun literalButton(internalName: String, label: String?, onClick: ThrowingRunnable<*>): BuiltButtonItem { var internalName = internalName internalName = prefix + internalName val action = SimpleAction(internalName, null) addAction(internalName, action) action.onActivate { _ -> try { onClick.run() } catch (e: Throwable) { Utils.LOGGER.error("Could not execute action", e) } } val menuItem = MenuItem(label, groupName + internalName) menu.appendItem(menuItem) action.enabled = true return BuiltButtonItem(action, menuItem) } fun toggle(name: String, initial: Boolean, onToggle: Consumer): BuiltToggleItem { var name = name name = prefix + name val action = SimpleAction.newStateful(name, null, Variant.newBoolean(initial)) addAction(name, action) action.onActivate { _ -> val state = !action.getState()!!.boolean action.state = Variant.newBoolean(state) onToggle.accept(state) } val menuItem = MenuItem(I18n["menu.$name"], groupName + name) menu.appendItem(menuItem) return BuiltToggleItem(action, menuItem) } fun radio(name: String, initial: T, options: List, onCheck: Consumer): BuiltRadioItem { return literalRadio(name, initial, options, { i, _ -> I18n["menu.$prefix$name", i] }, onCheck) } fun literalRadio( name: String, initial: T, options: List, stringifier: BiFunction, onCheck: Consumer ): BuiltRadioItem { var name = name name = prefix + name val action = SimpleAction.newStateful(name, VariantType("i"), Variant.newInt32(options.indexOf(initial))) addAction(name, action) action.onActivate { variant: Variant? -> action.state = variant onCheck.accept(options[variant!!.int32]) } for ((i, option) in options.withIndex()) { menu.appendItem(MenuItem(stringifier.apply(i, option), "$groupName$name($i)")) } return BuiltRadioItem(action, options) } fun submenu(name: String): MenuBuilder { return literalSubmenu(name, I18n["menu.$prefix$name"]) } fun literalSubmenu(name: String, label: String?): MenuBuilder { var name = name name = prefix + name val submenu = Menu() menu.appendSubmenu(label, submenu) return MenuBuilder(map, submenu, name, groupName) } fun section(name: String): MenuBuilder { return literalSection(name, I18n["section.$prefix$name"]) } fun literalSection(name: String, label: String?): MenuBuilder { var name = name name = prefix + name val section = Menu() menu.appendSection(label, section) return MenuBuilder(map, section, name, groupName) } fun clear() { menu.removeAll() refs.forEach { (name, _) -> map.removeAction(name) } refs.clear() } private fun addAction(name: String, action: SimpleAction) { map.addAction(action) refs[name] = action } fun asPopover(): PopoverMenu { return PopoverMenu.newFromModel(menu) } companion object { private val LOCK = Any() private fun getRootMenu(app: Application): Menu { synchronized(LOCK) { val currentMenu = app.menubar return if (currentMenu == null) { val menu = Menu() app.menubar = menu menu } else { currentMenu as Menu } } } fun create(target: MenuButton, groupName: String): MenuBuilder { val menu = Menu() val pm = PopoverMenu.newFromModel(menu) target.setPopover(pm) return MenuBuilder(pm, groupName) } private fun insertMap(menu: PopoverMenu, groupName: String): ActionMap { val ag = SimpleActionGroup() menu.insertActionGroup(groupName, ag) return ag } } }