refactor SignalListItemFactory usage with custom wrapper
This commit is contained in:
parent
b8f30247ea
commit
1759e5c3de
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue