Inceptum/launcher-gtk/src/main/kotlin/io/gitlab/jfronny/inceptum/gtk/menu/MenuBuilder.kt

171 lines
5.6 KiB
Kotlin

package io.gitlab.jfronny.inceptum.gtk.menu
import io.github.jwharm.javagi.glib.types.VariantTypes
import io.gitlab.jfronny.commons.throwable.ThrowingRunnable
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import io.gitlab.jfronny.inceptum.gtk.util.Log
import org.gnome.gio.*
import org.gnome.glib.Variant
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<String, Action> = 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) {
Log.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<Boolean?>): 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 <T> radio(name: String, initial: T, options: List<T>, onCheck: Consumer<T>): BuiltRadioItem<T> {
return literalRadio(name, initial, options, { i, _ -> I18n["menu.$prefix$name", i] }, onCheck)
}
fun <T> literalRadio(
name: String,
initial: T,
options: List<T>,
stringifier: BiFunction<Int, T, String?>,
onCheck: Consumer<T>
): BuiltRadioItem<T> {
var name = name
name = prefix + name
val action = SimpleAction.newStateful(name, VariantTypes.INT32, 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
}
}
}