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

224 lines
9.1 KiB
Kotlin

package io.gitlab.jfronny.inceptum.gtk
import io.gitlab.jfronny.commons.OSUtils
import io.gitlab.jfronny.commons.io.JFiles
import io.gitlab.jfronny.inceptum.common.MetaHolder
import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import io.gitlab.jfronny.inceptum.gtk.util.Log
import io.gitlab.jfronny.inceptum.gtk.window.AboutWindow
import io.gitlab.jfronny.inceptum.gtk.window.create.NewInstanceWindow
import io.gitlab.jfronny.inceptum.gtk.window.dialog.MicrosoftLoginDialog
import io.gitlab.jfronny.inceptum.gtk.window.dialog.ProcessStateWatcherDialog
import io.gitlab.jfronny.inceptum.gtk.window.settings.launcher.LauncherSettingsWindow
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv
import io.gitlab.jfronny.inceptum.launcher.api.account.AccountManager
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount
import io.gitlab.jfronny.inceptum.launcher.system.importer.Importers
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList
import io.gitlab.jfronny.inceptum.launcher.system.launch.InstanceLauncher
import io.gitlab.jfronny.inceptum.launcher.system.launch.LaunchType
import io.gitlab.jfronny.inceptum.launcher.system.setup.Steps
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState
import org.gnome.gio.Cancellable
import org.gnome.gio.Menu
import org.gnome.gtk.*
import java.awt.Toolkit
import java.awt.datatransfer.DataFlavor
import java.io.IOException
import java.nio.file.Path
object GtkMenubar {
@JvmField
var newMenu: MenuBuilder? = null
@JvmField
var accountsMenu: MenuBuilder? = null
@JvmField
var launchMenu: MenuBuilder? = null
@JvmStatic
fun create(app: Application) {
val menu = MenuBuilder(app)
val file = menu.submenu("file")
newMenu = file.submenu("new")
generateNewMenu(app)
file.button("redownload") {
val state = ProcessState(3 + Steps.STEPS.size * InstanceList.size(), "Initializing")
ProcessStateWatcherDialog.show(
GtkEnvBackend.dialogParent,
"Reloading data",
"Could not execute refresh task",
state
) {
state.incrementStep("Clearing cache directories")
JFiles.clearDirectory(MetaHolder.ASSETS_DIR)
JFiles.clearDirectory(MetaHolder.LIBRARIES_DIR) { path: Path -> !path.startsWith(MetaHolder.LIBRARIES_DIR.resolve("io/gitlab/jfronny")) }
JFiles.clearDirectory(MetaHolder.NATIVES_DIR) { path: Path -> !path.startsWith(MetaHolder.NATIVES_DIR.resolve("forceload")) }
JFiles.clearDirectory(MetaHolder.CACHE_DIR)
if (state.isCancelled) return@show
state.incrementStep("Reloading instance list")
InstanceList.reset()
InstanceList.forEach<IOException> { instance: Instance? ->
if (state.isCancelled) return@forEach
Steps.reDownload(instance, state)
}
}
}
file.button("exit") { app.quit() }
launchMenu = menu.submenu("launch")
generateLaunchMenu(app)
accountsMenu = MenuBuilder(app, Menu(), "account") // this should ideally be menu.submenu("account"), but that causes a segfault
generateAccountsMenu(app)
val help = menu.submenu("help")
help.button("about") { AboutWindow.createAndShow() }
help.button("log") {
//TODO
}
}
@JvmStatic
fun generateNewMenu(app: Application) {
newMenu!!.clear()
newMenu!!.button("new") { NewInstanceWindow(app).visible = true }
newMenu!!.button("file") {
val dialog = FileChooserNative(
I18n["menu.file.new.file"],
GtkEnvBackend.dialogParent,
FileChooserAction.OPEN,
"_" + I18n["select"],
"_" + I18n["cancel"]
)
val filter = FileFilter()
filter.addPattern("*.zip")
filter.addPattern("*.mrpack")
dialog.addFilter(filter)
dialog.onResponse { responseId: Int ->
if (responseId == ResponseType.ACCEPT.value) {
val file = dialog.file!!.path
if (file == null) {
LauncherEnv.showError("The path returned by the file dialog is null", "Could not import")
return@onResponse
}
val state = ProcessState(Importers.MAX_STEPS, "Initializing")
ProcessStateWatcherDialog.show(
GtkEnvBackend.dialogParent,
I18n["menu.file.new.file"],
I18n["menu.file.new.file.error"],
state
) {
Importers.importPack(Path.of(file), state)
}
}
}
dialog.show()
}
newMenu!!.button("url") {
readClipboard { clipboard ->
LauncherEnv.getInput(
I18n["menu.file.new.url"],
I18n["menu.file.new.url.details"],
clipboard ?: "",
{ s: String? ->
val state = ProcessState(Importers.MAX_STEPS, "Initializing")
ProcessStateWatcherDialog.show(
GtkEnvBackend.dialogParent,
I18n["menu.file.new.url"],
I18n["menu.file.new.url.error"],
state
) {
Importers.importPack(s, state)
}
}, {})
}
}
}
private fun readClipboard(continuation: (String?) -> Unit) {
if (OSUtils.TYPE == OSUtils.Type.LINUX) {
val clipboard = GtkEnvBackend.dialogParent!!.display.clipboard
clipboard.readTextAsync(Cancellable()) { _, res, _ ->
continuation(clipboard.readTextFinish(res))
}
} else {
continuation(Toolkit.getDefaultToolkit().systemClipboard.getData(DataFlavor.stringFlavor) as String?)
}
}
@JvmStatic
fun generateLaunchMenu(app: Application) {
launchMenu!!.clear()
try {
InstanceList.forEach<RuntimeException> { entry: Instance ->
launchMenu!!.literalButton(entry.id + ".launch", entry.toString()) {
launch(entry, LaunchType.Client)
}
}
} catch (e: IOException) {
Log.error("Could not generate launch menu", e)
}
}
@JvmStatic
fun launch(instance: Instance, launchType: LaunchType) {
if (instance.isSetupLocked) {
LauncherEnv.showError(I18n["instance.launch.locked.setup"], I18n["instance.launch.locked"])
} else if (instance.isRunningLocked) {
LauncherEnv.showOkCancel(
I18n["instance.launch.locked.running"],
I18n["instance.launch.locked"]
) { forceLaunch(instance, launchType) }
} else forceLaunch(instance, launchType)
}
private fun forceLaunch(instance: Instance, launchType: LaunchType) {
val state = Steps.createProcessState()
ProcessStateWatcherDialog.show(
GtkEnvBackend.dialogParent,
I18n["instance.launch.title"],
I18n["instance.launch.error"],
state
) {
try {
Steps.reDownload(instance, state)
} catch (e: IOException) {
Log.error("Could not fetch instance, trying to start anyways", e)
}
if (state.isCancelled) return@show
state.updateStep("Starting Game")
try {
if (launchType == LaunchType.Client) InstanceLauncher.launchClient(instance)
else InstanceLauncher.launch(
instance,
launchType,
false,
AccountManager.NULL_AUTH
)
} catch (e: Throwable) {
LauncherEnv.showError("Could not start instance", e)
}
}
}
@JvmStatic
fun generateAccountsMenu(app: Application) {
accountsMenu!!.clear()
accountsMenu!!.button("new") { MicrosoftLoginDialog(GtkEnvBackend.dialogParent).visible = true }
accountsMenu!!.button("manage") {
val window = LauncherSettingsWindow(app)
window.activePage = "settings.accounts"
window.visible = true
}
val accounts: MutableList<MicrosoftAccount?> = ArrayList(AccountManager.getAccounts())
accounts.add(null)
accountsMenu!!.literalRadio(
"account",
accounts[AccountManager.getSelectedIndex()],
accounts,
{ _, acc: MicrosoftAccount? ->
if (acc == null) return@literalRadio I18n["account.none"]
acc.minecraftUsername
}) { account: MicrosoftAccount? -> AccountManager.switchAccount(account) }
}
}