refactor SignalListItemFactory usage with custom wrapper

This commit is contained in:
Johannes Frohnmeyer 2023-07-14 16:19:37 +02:00
parent b8f30247ea
commit 1759e5c3de
Signed by: Johannes
GPG Key ID: E76429612C2929F4
4 changed files with 213 additions and 164 deletions

View File

@ -6,28 +6,28 @@ import org.gnome.gtk.*
import org.pango.EllipsizeMode
import org.pango.WrapMode
class InstanceGridEntryFactory(instanceList: List<Instance>) : SignalListItemFactory() {
init {
//TODO better design
onSetup { item ->
val box = Box(Orientation.VERTICAL, 5)
class InstanceGridEntryFactory(
private val instanceList: List<Instance>
) : KSignalListItemFactory<InstanceGridEntryFactory.Decomposed, Box>() {
override fun setup(): Box {
val box = Box(Orientation.VERTICAL, 5)
val thumbnail = InstanceThumbnail()
box.append(thumbnail)
val thumbnail = InstanceThumbnail()
box.append(thumbnail)
val label = Label(null as String?)
label.setSizeRequest(192, -1)
label.maxWidthChars = 20
label.justify = Justification.CENTER
label.halign = Align.START
label.hexpand = true
label.valign = Align.CENTER
label.ellipsize = EllipsizeMode.MIDDLE
label.lines = 3
label.wrap = true
label.wrapMode = WrapMode.WORD_CHAR
label.marginTop = 10
box.append(label)
val label = Label(null as String?)
label.setSizeRequest(192, -1)
label.maxWidthChars = 20
label.justify = Justification.CENTER
label.halign = Align.START
label.hexpand = true
label.valign = Align.CENTER
label.ellipsize = EllipsizeMode.MIDDLE
label.lines = 3
label.wrap = true
label.wrapMode = WrapMode.WORD_CHAR
label.marginTop = 10
box.append(label)
// Label label = new Label(Str.NULL);
// label.setXalign(0);
// label.setWidthChars(20);
@ -45,11 +45,12 @@ class InstanceGridEntryFactory(instanceList: List<Instance>) : SignalListItemFac
// openDir.setTooltipText(I18n.str("instance.directory"));
// openDir.setHasTooltip(GTK.TRUE);
// box.append(openDir);
(item as ListItem).setChild(box)
//TODO server launch with network-server-symbolic
//TODO kill current instance
}
onBind { item ->
//TODO server launch with network-server-symbolic
//TODO kill current instance
return box
}
override fun BindContext.bind(widget: Box, data: Decomposed) {
// Label label = new Label(item.getChild().getFirstChild().cast());
// Button launch = new Button(label.getNextSibling().cast());
// Button openDir = new Button(launch.getNextSibling().cast());
@ -57,16 +58,19 @@ class InstanceGridEntryFactory(instanceList: List<Instance>) : SignalListItemFac
// label.setText(new Str(instance.toString()));
// launch.onClicked(() -> GtkMenubar.launch(instance));
// openDir.onClicked(() -> Utils.openFile(instance.path().toFile()));
val thumbnail = InstanceThumbnail.castFrom(widget.firstChild as Stack)
val label = thumbnail.nextSibling as Label
val li = item as ListItem
val box = li.getChild() as Box
val thumbnail = InstanceThumbnail.castFrom(box.firstChild as Stack)
val label = thumbnail.nextSibling as Label
val instance = instanceList[(li.item as StringObject).string]
thumbnail.bind(instance!!)
label.text = instance.toString()
}
val instance = instanceList[data.id]
thumbnail.bind(instance!!)
label.text = instance.toString()
}
override fun UnbindContext.unbind(widget: Box, data: Decomposed) {
}
override fun ActionContext.castWidget(widget: Widget): Box = widget as Box
override fun ActionContext.lookup(id: String, widget: Box): Decomposed = Decomposed(id)
class Decomposed(val id: String)
}

View File

@ -1,6 +1,5 @@
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
@ -20,125 +19,128 @@ 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
class InstanceListEntryFactory(
private val app: Application?,
private val instanceList: List<Instance>
) : KSignalListItemFactory<InstanceListEntryFactory.Decomposed, ActionRow>() {
override fun setup(): ActionRow {
val thumbnail = InstanceThumbnail()
thumbnail.name = "inceptum-thumbnail"
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 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 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 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)
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() })
}
return row
}
private class Decomposed(
override fun BindContext.bind(widget: ActionRow, data: Decomposed) {
if (data.instance?.isLocked ?: true) {
data.item.activatable = false
data.row.subtitle = if (data.instance?.isRunningLocked ?: false) I18n["instance.launch.locked.running"]
else I18n["instance.launch.locked.setup"]
}
data.row.title = data.instance.toString()
data.thumbnail.bind(data.instance!!)
val menuBuilder = MenuBuilder(data.popoverMenu, data.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 (!data.instance.kill()) LauncherEnv.showError(I18n["instance.kill.fail"], I18n["failed"])
}
}
kill.enabled = data.instance.isRunningLocked
launchSection.literalButton(
"launch.client", I18n["instance.launch.client"]
) { GtkMenubar.launch(data.instance, LaunchType.Client) }.iconName = "media-playback-start-symbolic"
launchSection.literalButton(
"launch.server", I18n["instance.launch.server"]
) { GtkMenubar.launch(data.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, data.instance).visible = true
}.iconName = "document-edit-symbolic"
settingsSection.literalButton(
"directory", I18n["instance.directory"]
) { Utils.openFile(data.instance.path.toFile()) }.iconName = "folder-symbolic"
settingsSection.literalButton("copy", I18n["instance.copy"]) {
LauncherEnv.getInput(
I18n["instance.copy.prompt"],
I18n["instance.copy.details"],
InstanceNameTool.getNextValid(data.instance.name),
{ s: String? ->
try {
JFiles.copyRecursive(
data.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(data.instance.path)
} catch (e: IOException) {
LauncherEnv.showError(I18n["instance.delete.fail"], e)
}
}
}.iconName = "edit-delete-symbolic"
registerForUnbind(data.launch.onClicked { GtkMenubar.launch(data.instance, LaunchType.Client) })
}
override fun UnbindContext.unbind(widget: ActionRow, data: Decomposed) {
data.popoverMenu.insertActionGroup(data.instanceId, null)
}
override fun ActionContext.castWidget(widget: Widget): ActionRow = widget as ActionRow
override fun ActionContext.lookup(id: String, widget: ActionRow): Decomposed {
val instance = instanceList[id]
val prefixes = widget.firstChild!!.firstChild as Box
val suffixes = widget.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(listItem, id, instance, widget, thumbnail, launch, popoverMenu)
}
class Decomposed(
val item: ListItem,
val instanceId: String,
val instance: Instance?,
@ -146,20 +148,5 @@ class InstanceListEntryFactory(app: Application?, instanceList: List<Instance>)
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)
}
}
}
)
}

View File

@ -40,7 +40,7 @@ class InstanceThumbnail : Stack {
private const val SPINNER = "spinner"
private const val IMAGE = "image"
private const val GENERIC = "generic"
@JvmStatic
fun castFrom(stack: Stack): InstanceThumbnail = InstanceThumbnail(stack.handle())
}
}

View File

@ -0,0 +1,58 @@
package io.gitlab.jfronny.inceptum.gtk.control
import io.github.jwharm.javagi.base.Signal
import org.gnome.gtk.ListItem
import org.gnome.gtk.SignalListItemFactory
import org.gnome.gtk.StringObject
import org.gnome.gtk.Widget
abstract class KSignalListItemFactory<TData, TWidget : Widget> : SignalListItemFactory() {
private val toDisconnect: MutableMap<String, MutableSet<Signal<*>>> = HashMap()
init {
onSetup {
val li = it as ListItem
li.child = setup()
}
onBind {
val li = it as ListItem
val id = (li.item as StringObject).string
val context = BindContextImpl(id, li)
val widget = context.castWidget(li.child!!)
context.bind(widget, context.lookup(id, widget))
}
onUnbind {
val li = it as ListItem
val id = (li.item as StringObject).string
val context = UnbindContextImpl(li)
val widget = context.castWidget(li.child!!)
toDisconnect.remove(id)?.forEach { it.disconnect() }
context.unbind(widget, context.lookup(id, widget))
}
}
abstract fun setup(): TWidget
abstract fun BindContext.bind(widget: TWidget, data: TData)
abstract fun UnbindContext.unbind(widget: TWidget, data: TData)
abstract fun ActionContext.lookup(id: String, widget: TWidget): TData
abstract fun ActionContext.castWidget(widget: Widget): TWidget
interface ActionContext {
val listItem: ListItem
}
interface BindContext: ActionContext {
fun registerForUnbind(signal: Signal<*>)
}
interface UnbindContext: ActionContext {
}
private inner class BindContextImpl(private val id: String, override val listItem: ListItem) : BindContext {
override fun registerForUnbind(signal: Signal<*>) {
toDisconnect.computeIfAbsent(id) { _ -> HashSet() }
.add(signal)
}
}
private inner class UnbindContextImpl(override val listItem: ListItem): UnbindContext
}