Inceptum/launcher-gtk/src/main/kotlin/io/gitlab/jfronny/inceptum/gtk/control/InstanceListEntryFactory.kt

166 lines
7.3 KiB
Kotlin

package io.gitlab.jfronny.inceptum.gtk.control
import io.github.jwharm.javagi.base.Signal
import io.gitlab.jfronny.commons.io.JFiles
import io.gitlab.jfronny.commons.ref.R
import io.gitlab.jfronny.inceptum.common.MetaHolder
import io.gitlab.jfronny.inceptum.common.Utils
import io.gitlab.jfronny.inceptum.gtk.GtkMenubar
import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import io.gitlab.jfronny.inceptum.gtk.util.fixSubtitle
import io.gitlab.jfronny.inceptum.gtk.util.get
import io.gitlab.jfronny.inceptum.gtk.util.margin
import io.gitlab.jfronny.inceptum.gtk.window.settings.instance.InstanceSettingsWindow
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceNameTool
import io.gitlab.jfronny.inceptum.launcher.system.launch.LaunchType
import org.gnome.adw.ActionRow
import org.gnome.gio.Menu
import org.gnome.gtk.*
import java.io.IOException
import java.util.function.Consumer
class InstanceListEntryFactory(app: Application?, instanceList: List<Instance>) : SignalListItemFactory() {
init {
onSetup {
val li = it as ListItem
val thumbnail = InstanceThumbnail()
thumbnail.name = "inceptum-thumbnail"
val launch = Button.newFromIconName("media-playback-start-symbolic")
launch.addCssClass("flat")
launch.name = "inceptum-launch"
launch.tooltipText = I18n["instance.launch"]
launch.hasTooltip = true
val menu = MenuButton()
menu.addCssClass("flat")
menu.iconName = "view-more-symbolic"
menu.setPopover(PopoverMenu.newFromModel(Menu()))
val row = ActionRow()
row.margin = 8
row.name = "inceptum-row"
row.removeCssClass("activatable") //TODO remove this workaround if a better way to support opening the menu is found
row.addPrefix(thumbnail)
row.addSuffix(launch)
row.addSuffix(menu)
row.fixSubtitle()
val rightClicked = GestureClick()
rightClicked.button = 3
rightClicked.onPressed { nPress, _, _ -> if (nPress == 1) menu.emitActivate() }
row.addController(rightClicked)
li.child = row
}
val toDisconnect: MutableMap<String, MutableSet<Signal<*>>> = HashMap()
onBind {
val li = Decomposed.of(it as ListItem, instanceList)
if (li.instance?.isLocked ?: true) {
li.item.activatable = false
li.row.subtitle = if (li.instance?.isRunningLocked ?: false) I18n["instance.launch.locked.running"]
else I18n["instance.launch.locked.setup"]
}
li.row.title = li.instance.toString()
li.thumbnail.bind(li.instance!!)
val menuBuilder = MenuBuilder(li.popoverMenu, li.instanceId)
val launchSection = menuBuilder.literalSection("launch", null)
val kill = launchSection.literalButton("kill", I18n["instance.kill"]) {
//TODO test
LauncherEnv.showOkCancel(I18n["instance.kill.prompt"], I18n["instance.kill.details"]) {
if (!li.instance.kill()) LauncherEnv.showError(I18n["instance.kill.fail"], I18n["failed"])
}
}
kill.enabled = li.instance.isRunningLocked
launchSection.literalButton(
"launch.client", I18n["instance.launch.client"]
) { GtkMenubar.launch(li.instance, LaunchType.Client) }.iconName = "media-playback-start-symbolic"
launchSection.literalButton(
"launch.server", I18n["instance.launch.server"]
) { GtkMenubar.launch(li.instance, LaunchType.Server) }.iconName = "network-server-symbolic"
val settingsSection = menuBuilder.literalSection("settings", null)
settingsSection.literalButton("settings", I18n["instance.settings"]) {
//TODO keep track of properties windows and don't allow opening two
InstanceSettingsWindow(app, li.instance).visible = true
}.iconName = "document-edit-symbolic"
settingsSection.literalButton(
"directory", I18n["instance.directory"]
) { Utils.openFile(li.instance.path.toFile()) }.iconName = "folder-symbolic"
settingsSection.literalButton("copy", I18n["instance.copy"]) {
LauncherEnv.getInput(
I18n["instance.copy.prompt"],
I18n["instance.copy.details"],
InstanceNameTool.getNextValid(li.instance.name),
{ s: String? ->
try {
JFiles.copyRecursive(
li.instance.path,
MetaHolder.INSTANCE_DIR.resolve(InstanceNameTool.getNextValid(s))
)
} catch (e: IOException) {
LauncherEnv.showError(I18n["instance.copy.fail"], e)
}
}) { R.nop() }
}.iconName = "edit-copy-symbolic"
settingsSection.literalButton("delete", I18n["instance.delete"]) {
LauncherEnv.showOkCancel(
I18n["instance.delete.confirm"],
I18n["instance.delete.confirm.title"]
) {
try {
JFiles.deleteRecursive(li.instance.path)
} catch (e: IOException) {
LauncherEnv.showError(I18n["instance.delete.fail"], e)
}
}
}.iconName = "edit-delete-symbolic"
val dc = Consumer { s: Signal<*> ->
toDisconnect.computeIfAbsent(li.instanceId) { _ -> HashSet() }
.add(s)
}
dc.accept(li.launch.onClicked { GtkMenubar.launch(li.instance, LaunchType.Client) })
}
onUnbind {
val li = Decomposed.of(it as ListItem, instanceList)
li.popoverMenu.insertActionGroup(li.instanceId, null)
toDisconnect[li.instanceId]?.forEach(Consumer { obj: Signal<*> -> obj.disconnect() })
}
}
private class Decomposed(
val item: ListItem,
val instanceId: String,
val instance: Instance?,
val row: ActionRow,
val thumbnail: InstanceThumbnail,
val launch: Button,
val popoverMenu: PopoverMenu
) {
companion object {
fun of(item: ListItem, instanceList: List<Instance>): Decomposed {
val instanceId = (item.item as StringObject).string
val instance = instanceList[instanceId]
val row = item.child as ActionRow
val prefixes = row.firstChild!!.firstChild as Box
val suffixes = row.firstChild!!.lastChild as Box
val thumbnail = InstanceThumbnail.castFrom(prefixes.firstChild as Stack)
val launch = suffixes.firstChild as Button
val menuButton = launch.nextSibling as MenuButton
val popoverMenu = menuButton.popover as PopoverMenu
return Decomposed(item, instanceId, instance, row, thumbnail, launch, popoverMenu)
}
}
}
}