mirror of
				https://github.com/packwiz/packwiz-installer.git
				synced 2025-11-04 12:34:31 +01:00 
			
		
		
		
	Port UI to Kotlin
This commit is contained in:
		@@ -0,0 +1,47 @@
 | 
			
		||||
package link.infra.packwiz.installer.ui
 | 
			
		||||
 | 
			
		||||
import link.infra.packwiz.installer.ui.IExceptionDetails.ExceptionListResult
 | 
			
		||||
import java.util.concurrent.CompletableFuture
 | 
			
		||||
import java.util.concurrent.Future
 | 
			
		||||
 | 
			
		||||
class CLIHandler : IUserInterface {
 | 
			
		||||
	override fun handleException(e: Exception) {
 | 
			
		||||
		e.printStackTrace()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun show(handler: InputStateHandler) {}
 | 
			
		||||
	override fun submitProgress(progress: InstallProgress) {
 | 
			
		||||
		val sb = StringBuilder()
 | 
			
		||||
		if (progress.hasProgress) {
 | 
			
		||||
			sb.append('(')
 | 
			
		||||
			sb.append(progress.progress)
 | 
			
		||||
			sb.append('/')
 | 
			
		||||
			sb.append(progress.progressTotal)
 | 
			
		||||
			sb.append(") ")
 | 
			
		||||
		}
 | 
			
		||||
		sb.append(progress.message)
 | 
			
		||||
		println(sb.toString())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun executeManager(task: () -> Unit) {
 | 
			
		||||
		task()
 | 
			
		||||
		println("Finished successfully!")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun showOptions(options: List<IOptionDetails>): Future<Boolean> {
 | 
			
		||||
		for (opt in options) {
 | 
			
		||||
			opt.optionValue = true
 | 
			
		||||
			// TODO: implement option choice in the CLI?
 | 
			
		||||
			println("Warning: accepting option " + opt.name + " as option choosing is not implemented in the CLI")
 | 
			
		||||
		}
 | 
			
		||||
		return CompletableFuture<Boolean>().apply {
 | 
			
		||||
			complete(false) // Can't be cancelled!
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun showExceptions(exceptions: List<IExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): Future<ExceptionListResult> {
 | 
			
		||||
		val future = CompletableFuture<ExceptionListResult>()
 | 
			
		||||
		future.complete(ExceptionListResult.CANCEL)
 | 
			
		||||
		return future
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,152 @@
 | 
			
		||||
package link.infra.packwiz.installer.ui
 | 
			
		||||
 | 
			
		||||
import link.infra.packwiz.installer.ui.IExceptionDetails.ExceptionListResult
 | 
			
		||||
import java.awt.BorderLayout
 | 
			
		||||
import java.awt.Desktop
 | 
			
		||||
import java.awt.event.WindowAdapter
 | 
			
		||||
import java.awt.event.WindowEvent
 | 
			
		||||
import java.io.IOException
 | 
			
		||||
import java.io.PrintWriter
 | 
			
		||||
import java.io.StringWriter
 | 
			
		||||
import java.net.URI
 | 
			
		||||
import java.net.URISyntaxException
 | 
			
		||||
import java.util.concurrent.CompletableFuture
 | 
			
		||||
import javax.swing.*
 | 
			
		||||
import javax.swing.border.EmptyBorder
 | 
			
		||||
 | 
			
		||||
internal class ExceptionListWindow(eList: List<IExceptionDetails>, future: CompletableFuture<ExceptionListResult>, numTotal: Int, allowsIgnore: Boolean, parentWindow: JFrame?) : JDialog(parentWindow, "Failed file downloads", true) {
 | 
			
		||||
	private val lblExceptionStacktrace: JTextArea
 | 
			
		||||
 | 
			
		||||
	private class ExceptionListModel internal constructor(private val details: List<IExceptionDetails>) : AbstractListModel<String>() {
 | 
			
		||||
		override fun getSize() = details.size
 | 
			
		||||
		override fun getElementAt(index: Int) = details[index].name
 | 
			
		||||
		fun getExceptionAt(index: Int) = details[index].exception
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Create the dialog.
 | 
			
		||||
	 */
 | 
			
		||||
	init {
 | 
			
		||||
		setBounds(100, 100, 540, 340)
 | 
			
		||||
		setLocationRelativeTo(parentWindow)
 | 
			
		||||
 | 
			
		||||
		contentPane.apply {
 | 
			
		||||
			layout = BorderLayout()
 | 
			
		||||
 | 
			
		||||
			// Error panel
 | 
			
		||||
			add(JPanel().apply {
 | 
			
		||||
				add(JLabel("One or more errors were encountered while installing the modpack!").apply {
 | 
			
		||||
					icon = UIManager.getIcon("OptionPane.warningIcon")
 | 
			
		||||
				})
 | 
			
		||||
			}, BorderLayout.NORTH)
 | 
			
		||||
 | 
			
		||||
			// Content panel
 | 
			
		||||
			add(JPanel().apply {
 | 
			
		||||
				border = EmptyBorder(5, 5, 5, 5)
 | 
			
		||||
				layout = BorderLayout(0, 0)
 | 
			
		||||
 | 
			
		||||
				add(JSplitPane().apply {
 | 
			
		||||
					resizeWeight = 0.3
 | 
			
		||||
 | 
			
		||||
					lblExceptionStacktrace = JTextArea("Select a file")
 | 
			
		||||
					lblExceptionStacktrace.background = UIManager.getColor("List.background")
 | 
			
		||||
					lblExceptionStacktrace.isOpaque = true
 | 
			
		||||
					lblExceptionStacktrace.wrapStyleWord = true
 | 
			
		||||
					lblExceptionStacktrace.lineWrap = true
 | 
			
		||||
					lblExceptionStacktrace.isEditable = false
 | 
			
		||||
					lblExceptionStacktrace.isFocusable = true
 | 
			
		||||
					lblExceptionStacktrace.font = UIManager.getFont("Label.font")
 | 
			
		||||
					lblExceptionStacktrace.border = EmptyBorder(5, 5, 5, 5)
 | 
			
		||||
 | 
			
		||||
					rightComponent = JScrollPane(lblExceptionStacktrace)
 | 
			
		||||
 | 
			
		||||
					leftComponent = JScrollPane(JList<String>().apply {
 | 
			
		||||
						selectionMode = ListSelectionModel.SINGLE_SELECTION
 | 
			
		||||
						border = EmptyBorder(5, 5, 5, 5)
 | 
			
		||||
						val listModel = ExceptionListModel(eList)
 | 
			
		||||
						model = listModel
 | 
			
		||||
						addListSelectionListener {
 | 
			
		||||
							val i = selectedIndex
 | 
			
		||||
							if (i > -1) {
 | 
			
		||||
								val sw = StringWriter()
 | 
			
		||||
								listModel.getExceptionAt(i).printStackTrace(PrintWriter(sw))
 | 
			
		||||
								lblExceptionStacktrace.text = sw.toString()
 | 
			
		||||
								// Scroll to the top
 | 
			
		||||
								lblExceptionStacktrace.caretPosition = 0
 | 
			
		||||
							} else {
 | 
			
		||||
								lblExceptionStacktrace.text = "Select a file"
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					})
 | 
			
		||||
				})
 | 
			
		||||
			}, BorderLayout.CENTER)
 | 
			
		||||
 | 
			
		||||
			// Button pane
 | 
			
		||||
			add(JPanel().apply {
 | 
			
		||||
				layout = BorderLayout(0, 0)
 | 
			
		||||
 | 
			
		||||
				// Right buttons
 | 
			
		||||
				add(JPanel().apply {
 | 
			
		||||
					add(JButton("Continue").apply {
 | 
			
		||||
						toolTipText = "Attempt to continue installing, excluding the failed downloads"
 | 
			
		||||
						addActionListener {
 | 
			
		||||
							future.complete(ExceptionListResult.CONTINUE)
 | 
			
		||||
							this@ExceptionListWindow.dispose()
 | 
			
		||||
						}
 | 
			
		||||
					})
 | 
			
		||||
 | 
			
		||||
					add(JButton("Cancel launch").apply {
 | 
			
		||||
						toolTipText = "Stop launching the game"
 | 
			
		||||
						addActionListener {
 | 
			
		||||
							future.complete(ExceptionListResult.CANCEL)
 | 
			
		||||
							this@ExceptionListWindow.dispose()
 | 
			
		||||
						}
 | 
			
		||||
					})
 | 
			
		||||
 | 
			
		||||
					add(JButton("Ignore update").apply {
 | 
			
		||||
						toolTipText = "Start the game without attempting to update"
 | 
			
		||||
						isEnabled = allowsIgnore
 | 
			
		||||
						addActionListener {
 | 
			
		||||
							future.complete(ExceptionListResult.IGNORE)
 | 
			
		||||
							this@ExceptionListWindow.dispose()
 | 
			
		||||
						}
 | 
			
		||||
					})
 | 
			
		||||
				}, BorderLayout.EAST)
 | 
			
		||||
 | 
			
		||||
				// Errored label
 | 
			
		||||
				add(JLabel(eList.size.toString() + "/" + numTotal + " errored").apply {
 | 
			
		||||
					horizontalAlignment = SwingConstants.CENTER
 | 
			
		||||
				}, BorderLayout.CENTER)
 | 
			
		||||
 | 
			
		||||
				// Left buttons
 | 
			
		||||
				add(JPanel().apply {
 | 
			
		||||
					add(JButton("Report issue").apply {
 | 
			
		||||
						if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
 | 
			
		||||
							addActionListener {
 | 
			
		||||
								try {
 | 
			
		||||
									Desktop.getDesktop().browse(URI("https://github.com/comp500/packwiz-installer/issues/new"))
 | 
			
		||||
								} catch (e: IOException) {
 | 
			
		||||
									// lol the button just won't work i guess
 | 
			
		||||
								} catch (e: URISyntaxException) {}
 | 
			
		||||
							}
 | 
			
		||||
						} else {
 | 
			
		||||
							isEnabled = false
 | 
			
		||||
						}
 | 
			
		||||
					})
 | 
			
		||||
				}, BorderLayout.WEST)
 | 
			
		||||
			}, BorderLayout.SOUTH)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		addWindowListener(object : WindowAdapter() {
 | 
			
		||||
			override fun windowClosing(e: WindowEvent) {
 | 
			
		||||
				future.complete(ExceptionListResult.CANCEL)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			override fun windowClosed(e: WindowEvent) {
 | 
			
		||||
				// Just in case closing didn't get triggered - if something else called dispose() the
 | 
			
		||||
				// future will have already completed
 | 
			
		||||
				future.complete(ExceptionListResult.CANCEL)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
package link.infra.packwiz.installer.ui
 | 
			
		||||
 | 
			
		||||
interface IExceptionDetails {
 | 
			
		||||
	val exception: Exception
 | 
			
		||||
	val name: String
 | 
			
		||||
 | 
			
		||||
	enum class ExceptionListResult {
 | 
			
		||||
		CONTINUE, CANCEL, IGNORE
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,7 @@
 | 
			
		||||
package link.infra.packwiz.installer.ui
 | 
			
		||||
 | 
			
		||||
interface IOptionDetails {
 | 
			
		||||
	val name: String
 | 
			
		||||
	var optionValue: Boolean
 | 
			
		||||
	val optionDescription: String
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
package link.infra.packwiz.installer.ui
 | 
			
		||||
 | 
			
		||||
import link.infra.packwiz.installer.ui.IExceptionDetails.ExceptionListResult
 | 
			
		||||
import java.util.concurrent.CompletableFuture
 | 
			
		||||
import java.util.concurrent.Future
 | 
			
		||||
import kotlin.system.exitProcess
 | 
			
		||||
 | 
			
		||||
interface IUserInterface {
 | 
			
		||||
	fun show(handler: InputStateHandler)
 | 
			
		||||
	fun handleException(e: Exception)
 | 
			
		||||
	@JvmDefault
 | 
			
		||||
	fun handleExceptionAndExit(e: Exception) {
 | 
			
		||||
		handleException(e)
 | 
			
		||||
		exitProcess(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@JvmDefault
 | 
			
		||||
	fun setTitle(title: String) {}
 | 
			
		||||
	fun submitProgress(progress: InstallProgress)
 | 
			
		||||
	fun executeManager(task: () -> Unit)
 | 
			
		||||
	// Return true if the installation was cancelled!
 | 
			
		||||
	fun showOptions(options: List<IOptionDetails>): Future<Boolean>
 | 
			
		||||
 | 
			
		||||
	fun showExceptions(exceptions: List<IExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): Future<ExceptionListResult>
 | 
			
		||||
	@JvmDefault
 | 
			
		||||
	fun disableOptionsButton() {}
 | 
			
		||||
	// Should not return CONTINUE
 | 
			
		||||
	@JvmDefault
 | 
			
		||||
	fun showCancellationDialog(): Future<ExceptionListResult> {
 | 
			
		||||
		return CompletableFuture<ExceptionListResult>().apply {
 | 
			
		||||
			complete(ExceptionListResult.CANCEL)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,21 @@
 | 
			
		||||
package link.infra.packwiz.installer.ui
 | 
			
		||||
 | 
			
		||||
class InputStateHandler {
 | 
			
		||||
	// TODO: convert to coroutines/locks?
 | 
			
		||||
	@get:Synchronized
 | 
			
		||||
	var optionsButton = false
 | 
			
		||||
		private set
 | 
			
		||||
	@get:Synchronized
 | 
			
		||||
	var cancelButton = false
 | 
			
		||||
		private set
 | 
			
		||||
 | 
			
		||||
	@Synchronized
 | 
			
		||||
	fun pressCancelButton() {
 | 
			
		||||
		cancelButton = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Synchronized
 | 
			
		||||
	fun pressOptionsButton() {
 | 
			
		||||
		optionsButton = true
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
package link.infra.packwiz.installer.ui
 | 
			
		||||
 | 
			
		||||
data class InstallProgress(
 | 
			
		||||
		val message: String,
 | 
			
		||||
		val hasProgress: Boolean = false,
 | 
			
		||||
		val progress: Int = 0,
 | 
			
		||||
		val progressTotal: Int = 0
 | 
			
		||||
) {
 | 
			
		||||
	constructor(message: String, progress: Int, progressTotal: Int) : this(message, true, progress, progressTotal)
 | 
			
		||||
 | 
			
		||||
	constructor(message: String) : this(message, false)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										219
									
								
								src/main/kotlin/link/infra/packwiz/installer/ui/InstallWindow.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								src/main/kotlin/link/infra/packwiz/installer/ui/InstallWindow.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,219 @@
 | 
			
		||||
package link.infra.packwiz.installer.ui
 | 
			
		||||
 | 
			
		||||
import link.infra.packwiz.installer.ui.IExceptionDetails.ExceptionListResult
 | 
			
		||||
import java.awt.*
 | 
			
		||||
import java.util.concurrent.CompletableFuture
 | 
			
		||||
import java.util.concurrent.Future
 | 
			
		||||
import java.util.concurrent.atomic.AtomicBoolean
 | 
			
		||||
import javax.swing.*
 | 
			
		||||
import javax.swing.border.EmptyBorder
 | 
			
		||||
import kotlin.system.exitProcess
 | 
			
		||||
 | 
			
		||||
class InstallWindow : IUserInterface {
 | 
			
		||||
	private val frmPackwizlauncher: JFrame
 | 
			
		||||
	private val lblProgresslabel: JLabel
 | 
			
		||||
	private val progressBar: JProgressBar
 | 
			
		||||
	private val btnOptions: JButton
 | 
			
		||||
 | 
			
		||||
	private var inputStateHandler: InputStateHandler? = null
 | 
			
		||||
	private var title = "Updating modpack..."
 | 
			
		||||
	private var worker: SwingWorkerButWithPublicPublish<Unit, InstallProgress>? = null
 | 
			
		||||
	private val aboutToCrash = AtomicBoolean()
 | 
			
		||||
 | 
			
		||||
	// TODO: separate JFrame junk from IUserInterface junk?
 | 
			
		||||
 | 
			
		||||
	init {
 | 
			
		||||
		frmPackwizlauncher = JFrame().apply {
 | 
			
		||||
			title = this@InstallWindow.title
 | 
			
		||||
			setBounds(100, 100, 493, 95)
 | 
			
		||||
			defaultCloseOperation = JFrame.EXIT_ON_CLOSE
 | 
			
		||||
			setLocationRelativeTo(null)
 | 
			
		||||
 | 
			
		||||
			// Progress bar and loading text
 | 
			
		||||
			add(JPanel().apply {
 | 
			
		||||
				border = EmptyBorder(10, 10, 10, 10)
 | 
			
		||||
				layout = BorderLayout(0, 0)
 | 
			
		||||
 | 
			
		||||
				progressBar = JProgressBar().apply {
 | 
			
		||||
					isIndeterminate = true
 | 
			
		||||
				}
 | 
			
		||||
				add(progressBar, BorderLayout.CENTER)
 | 
			
		||||
 | 
			
		||||
				lblProgresslabel = JLabel("Loading...")
 | 
			
		||||
				add(lblProgresslabel, BorderLayout.SOUTH)
 | 
			
		||||
			}, BorderLayout.CENTER)
 | 
			
		||||
 | 
			
		||||
			// Buttons
 | 
			
		||||
			add(JPanel().apply {
 | 
			
		||||
				border = EmptyBorder(0, 5, 0, 5)
 | 
			
		||||
				layout = GridBagLayout()
 | 
			
		||||
 | 
			
		||||
				btnOptions = JButton("Optional mods...").apply {
 | 
			
		||||
					alignmentX = Component.CENTER_ALIGNMENT
 | 
			
		||||
 | 
			
		||||
					addActionListener {
 | 
			
		||||
						text = "Loading..."
 | 
			
		||||
						isEnabled = false
 | 
			
		||||
						inputStateHandler?.pressOptionsButton()
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				add(btnOptions, GridBagConstraints().apply {
 | 
			
		||||
					gridx = 0
 | 
			
		||||
					gridy = 0
 | 
			
		||||
				})
 | 
			
		||||
 | 
			
		||||
				add(JButton("Cancel").apply {
 | 
			
		||||
					addActionListener {
 | 
			
		||||
						isEnabled = false
 | 
			
		||||
						inputStateHandler?.pressCancelButton()
 | 
			
		||||
					}
 | 
			
		||||
				}, GridBagConstraints().apply {
 | 
			
		||||
					gridx = 0
 | 
			
		||||
					gridy = 1
 | 
			
		||||
				})
 | 
			
		||||
			}, BorderLayout.EAST)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun show(handler: InputStateHandler) {
 | 
			
		||||
		inputStateHandler = handler
 | 
			
		||||
		EventQueue.invokeLater {
 | 
			
		||||
			try {
 | 
			
		||||
				UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
 | 
			
		||||
				frmPackwizlauncher.isVisible = true
 | 
			
		||||
			} catch (e: Exception) {
 | 
			
		||||
				e.printStackTrace()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun handleException(e: Exception) {
 | 
			
		||||
		e.printStackTrace()
 | 
			
		||||
		EventQueue.invokeLater {
 | 
			
		||||
			JOptionPane.showMessageDialog(null,
 | 
			
		||||
					"An error occurred: \n" + e.javaClass.canonicalName + ": " + e.message,
 | 
			
		||||
					title, JOptionPane.ERROR_MESSAGE)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun handleExceptionAndExit(e: Exception) {
 | 
			
		||||
		e.printStackTrace()
 | 
			
		||||
		// TODO: Fix this mess
 | 
			
		||||
		// Used to prevent the done() handler of SwingWorker executing if the invokeLater hasn't happened yet
 | 
			
		||||
		aboutToCrash.set(true)
 | 
			
		||||
		EventQueue.invokeLater {
 | 
			
		||||
			JOptionPane.showMessageDialog(null,
 | 
			
		||||
					"A fatal error occurred: \n" + e.javaClass.canonicalName + ": " + e.message,
 | 
			
		||||
					title, JOptionPane.ERROR_MESSAGE)
 | 
			
		||||
			exitProcess(1)
 | 
			
		||||
		}
 | 
			
		||||
		// Pause forever, so it blocks while we wait for System.exit to take effect
 | 
			
		||||
		try {
 | 
			
		||||
			Thread.currentThread().join()
 | 
			
		||||
		} catch (ex: InterruptedException) { // no u
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun setTitle(title: String) {
 | 
			
		||||
		this.title = title
 | 
			
		||||
		frmPackwizlauncher.let { frame ->
 | 
			
		||||
			EventQueue.invokeLater { frame.title = title }
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun submitProgress(progress: InstallProgress) {
 | 
			
		||||
		val sb = StringBuilder()
 | 
			
		||||
		if (progress.hasProgress) {
 | 
			
		||||
			sb.append('(')
 | 
			
		||||
			sb.append(progress.progress)
 | 
			
		||||
			sb.append('/')
 | 
			
		||||
			sb.append(progress.progressTotal)
 | 
			
		||||
			sb.append(") ")
 | 
			
		||||
		}
 | 
			
		||||
		sb.append(progress.message)
 | 
			
		||||
		// TODO: better logging library?
 | 
			
		||||
		println(sb.toString())
 | 
			
		||||
		worker?.publishPublic(progress)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun executeManager(task: Function0<Unit>) {
 | 
			
		||||
		EventQueue.invokeLater {
 | 
			
		||||
			// TODO: rewrite this stupidity to use channels??!!!
 | 
			
		||||
			worker = object : SwingWorkerButWithPublicPublish<Unit, InstallProgress>() {
 | 
			
		||||
				override fun doInBackground() {
 | 
			
		||||
					task.invoke()
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				override fun process(chunks: List<InstallProgress>) {
 | 
			
		||||
					// Only process last chunk
 | 
			
		||||
					if (chunks.isNotEmpty()) {
 | 
			
		||||
						val (message, hasProgress, progress, progressTotal) = chunks[chunks.size - 1]
 | 
			
		||||
						if (hasProgress) {
 | 
			
		||||
							progressBar.isIndeterminate = false
 | 
			
		||||
							progressBar.value = progress
 | 
			
		||||
							progressBar.maximum = progressTotal
 | 
			
		||||
						} else {
 | 
			
		||||
							progressBar.isIndeterminate = true
 | 
			
		||||
							progressBar.value = 0
 | 
			
		||||
						}
 | 
			
		||||
						lblProgresslabel.text = message
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				override fun done() {
 | 
			
		||||
					if (aboutToCrash.get()) {
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					// TODO: a better way to do this?
 | 
			
		||||
					frmPackwizlauncher.dispose()
 | 
			
		||||
					println("Finished successfully!")
 | 
			
		||||
					exitProcess(0)
 | 
			
		||||
				}
 | 
			
		||||
			}.also {
 | 
			
		||||
				it.execute()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun showOptions(options: List<IOptionDetails>): Future<Boolean> {
 | 
			
		||||
		val future = CompletableFuture<Boolean>()
 | 
			
		||||
		EventQueue.invokeLater {
 | 
			
		||||
			OptionsSelectWindow(options, future, frmPackwizlauncher).apply {
 | 
			
		||||
				defaultCloseOperation = JDialog.DISPOSE_ON_CLOSE
 | 
			
		||||
				isVisible = true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return future
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun showExceptions(exceptions: List<IExceptionDetails>, numTotal: Int, allowsIgnore: Boolean): Future<ExceptionListResult> {
 | 
			
		||||
		val future = CompletableFuture<ExceptionListResult>()
 | 
			
		||||
		EventQueue.invokeLater {
 | 
			
		||||
			ExceptionListWindow(exceptions, future, numTotal, allowsIgnore, frmPackwizlauncher).apply {
 | 
			
		||||
				defaultCloseOperation = JDialog.DISPOSE_ON_CLOSE
 | 
			
		||||
				isVisible = true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return future
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun disableOptionsButton() {
 | 
			
		||||
		btnOptions.apply {
 | 
			
		||||
			text = "Optional mods..."
 | 
			
		||||
			isEnabled = false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun showCancellationDialog(): Future<ExceptionListResult> {
 | 
			
		||||
		val future = CompletableFuture<ExceptionListResult>()
 | 
			
		||||
		EventQueue.invokeLater {
 | 
			
		||||
			val buttons = arrayOf("Quit", "Ignore")
 | 
			
		||||
			val result = JOptionPane.showOptionDialog(frmPackwizlauncher,
 | 
			
		||||
					"The installation was cancelled. Would you like to quit the game, or ignore the update and start the game?",
 | 
			
		||||
					"Cancelled installation",
 | 
			
		||||
					JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, buttons, buttons[0])
 | 
			
		||||
			future.complete(if (result == 0) ExceptionListResult.CANCEL else ExceptionListResult.IGNORE)
 | 
			
		||||
		}
 | 
			
		||||
		return future
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
package link.infra.packwiz.installer.ui
 | 
			
		||||
 | 
			
		||||
// Serves as a proxy for IOptionDetails, so that setOptionValue isn't called until OK is clicked
 | 
			
		||||
internal class OptionTempHandler(private val opt: IOptionDetails) : IOptionDetails {
 | 
			
		||||
	override var optionValue = opt.optionValue
 | 
			
		||||
 | 
			
		||||
	override val name get() = opt.name
 | 
			
		||||
	override val optionDescription get() = opt.optionDescription
 | 
			
		||||
 | 
			
		||||
	fun finalise() {
 | 
			
		||||
		opt.optionValue = optionValue
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,166 @@
 | 
			
		||||
package link.infra.packwiz.installer.ui
 | 
			
		||||
 | 
			
		||||
import java.awt.BorderLayout
 | 
			
		||||
import java.awt.FlowLayout
 | 
			
		||||
import java.awt.event.ActionEvent
 | 
			
		||||
import java.awt.event.ActionListener
 | 
			
		||||
import java.awt.event.WindowAdapter
 | 
			
		||||
import java.awt.event.WindowEvent
 | 
			
		||||
import java.util.*
 | 
			
		||||
import java.util.concurrent.CompletableFuture
 | 
			
		||||
import javax.swing.*
 | 
			
		||||
import javax.swing.border.EmptyBorder
 | 
			
		||||
import javax.swing.event.TableModelListener
 | 
			
		||||
import javax.swing.table.TableModel
 | 
			
		||||
 | 
			
		||||
class OptionsSelectWindow internal constructor(optList: List<IOptionDetails>, future: CompletableFuture<Boolean>, parentWindow: JFrame?) : JDialog(parentWindow, "Select optional mods...", true), ActionListener {
 | 
			
		||||
	private val lblOptionDescription: JTextArea
 | 
			
		||||
	private val tableModel: OptionTableModel
 | 
			
		||||
	private val future: CompletableFuture<Boolean>
 | 
			
		||||
 | 
			
		||||
	private class OptionTableModel internal constructor(givenOpts: List<IOptionDetails>) : TableModel {
 | 
			
		||||
		private val opts: List<OptionTempHandler>
 | 
			
		||||
 | 
			
		||||
		init {
 | 
			
		||||
			val mutOpts = ArrayList<OptionTempHandler>()
 | 
			
		||||
			for (opt in givenOpts) {
 | 
			
		||||
				mutOpts.add(OptionTempHandler(opt))
 | 
			
		||||
			}
 | 
			
		||||
			opts = mutOpts
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		override fun getRowCount() = opts.size
 | 
			
		||||
		override fun getColumnCount() = 2
 | 
			
		||||
 | 
			
		||||
		private val columnNames = arrayOf("Enabled", "Mod name")
 | 
			
		||||
		private val columnTypes = arrayOf(Boolean::class.javaObjectType, String::class.java)
 | 
			
		||||
		private val columnEditables = booleanArrayOf(true, false)
 | 
			
		||||
 | 
			
		||||
		override fun getColumnName(columnIndex: Int) = columnNames[columnIndex]
 | 
			
		||||
		override fun getColumnClass(columnIndex: Int) = columnTypes[columnIndex]
 | 
			
		||||
		override fun isCellEditable(rowIndex: Int, columnIndex: Int) = columnEditables[columnIndex]
 | 
			
		||||
 | 
			
		||||
		override fun getValueAt(rowIndex: Int, columnIndex: Int): Any {
 | 
			
		||||
			val opt = opts[rowIndex]
 | 
			
		||||
			return if (columnIndex == 0) opt.optionValue else opt.name
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		override fun setValueAt(aValue: Any, rowIndex: Int, columnIndex: Int) {
 | 
			
		||||
			if (columnIndex == 0) {
 | 
			
		||||
				val opt = opts[rowIndex]
 | 
			
		||||
				opt.optionValue = aValue as Boolean
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Noop, the table model doesn't change!
 | 
			
		||||
		override fun addTableModelListener(l: TableModelListener) {}
 | 
			
		||||
		override fun removeTableModelListener(l: TableModelListener) {}
 | 
			
		||||
 | 
			
		||||
		fun getDescription(rowIndex: Int) = opts[rowIndex].optionDescription
 | 
			
		||||
 | 
			
		||||
		fun finalise() {
 | 
			
		||||
			for (opt in opts) {
 | 
			
		||||
				opt.finalise()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override fun actionPerformed(e: ActionEvent) {
 | 
			
		||||
		if (e.actionCommand == "OK") {
 | 
			
		||||
			tableModel.finalise()
 | 
			
		||||
			future.complete(false)
 | 
			
		||||
			dispose()
 | 
			
		||||
		} else if (e.actionCommand == "Cancel") {
 | 
			
		||||
			future.complete(true)
 | 
			
		||||
			dispose()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Create the dialog.
 | 
			
		||||
	 */
 | 
			
		||||
	init {
 | 
			
		||||
		tableModel = OptionTableModel(optList)
 | 
			
		||||
		this.future = future
 | 
			
		||||
 | 
			
		||||
		setBounds(100, 100, 450, 300)
 | 
			
		||||
		setLocationRelativeTo(parentWindow)
 | 
			
		||||
 | 
			
		||||
		contentPane.apply {
 | 
			
		||||
			layout = BorderLayout()
 | 
			
		||||
			add(JPanel().apply {
 | 
			
		||||
				border = EmptyBorder(5, 5, 5, 5)
 | 
			
		||||
				layout = BorderLayout(0, 0)
 | 
			
		||||
 | 
			
		||||
				add(JSplitPane().apply {
 | 
			
		||||
					resizeWeight = 0.5
 | 
			
		||||
 | 
			
		||||
					lblOptionDescription = JTextArea("Select an option...").apply {
 | 
			
		||||
						background = UIManager.getColor("List.background")
 | 
			
		||||
						isOpaque = true
 | 
			
		||||
						wrapStyleWord = true
 | 
			
		||||
						lineWrap = true
 | 
			
		||||
						isEditable = false
 | 
			
		||||
						isFocusable = false
 | 
			
		||||
						font = UIManager.getFont("Label.font")
 | 
			
		||||
						border = EmptyBorder(10, 10, 10, 10)
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					leftComponent = JScrollPane(JTable().apply {
 | 
			
		||||
						showVerticalLines = false
 | 
			
		||||
						showHorizontalLines = false
 | 
			
		||||
						setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
 | 
			
		||||
						setShowGrid(false)
 | 
			
		||||
						model = tableModel
 | 
			
		||||
						columnModel.getColumn(0).resizable = false
 | 
			
		||||
						columnModel.getColumn(0).preferredWidth = 15
 | 
			
		||||
						columnModel.getColumn(0).maxWidth = 15
 | 
			
		||||
						columnModel.getColumn(1).resizable = false
 | 
			
		||||
						selectionModel.addListSelectionListener {
 | 
			
		||||
							val i = selectedRow
 | 
			
		||||
							if (i > -1) {
 | 
			
		||||
								lblOptionDescription.text = tableModel.getDescription(i)
 | 
			
		||||
							} else {
 | 
			
		||||
								lblOptionDescription.text = "Select an option..."
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						tableHeader = null
 | 
			
		||||
					}).apply {
 | 
			
		||||
						viewport.background = UIManager.getColor("List.background")
 | 
			
		||||
						horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					rightComponent = JScrollPane(lblOptionDescription)
 | 
			
		||||
				})
 | 
			
		||||
 | 
			
		||||
				add(JPanel().apply {
 | 
			
		||||
					layout = FlowLayout(FlowLayout.RIGHT)
 | 
			
		||||
 | 
			
		||||
					add(JButton("OK").apply {
 | 
			
		||||
						actionCommand = "OK"
 | 
			
		||||
						addActionListener(this@OptionsSelectWindow)
 | 
			
		||||
 | 
			
		||||
						this@OptionsSelectWindow.rootPane.defaultButton = this
 | 
			
		||||
					})
 | 
			
		||||
 | 
			
		||||
					add(JButton("Cancel").apply {
 | 
			
		||||
						actionCommand = "Cancel"
 | 
			
		||||
						addActionListener(this@OptionsSelectWindow)
 | 
			
		||||
					})
 | 
			
		||||
				}, BorderLayout.SOUTH)
 | 
			
		||||
			}, BorderLayout.CENTER)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		addWindowListener(object : WindowAdapter() {
 | 
			
		||||
			override fun windowClosing(e: WindowEvent) {
 | 
			
		||||
				future.complete(true)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			override fun windowClosed(e: WindowEvent) {
 | 
			
		||||
				// Just in case closing didn't get triggered - if something else called dispose() the
 | 
			
		||||
				// future will have already completed
 | 
			
		||||
				future.complete(true)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
package link.infra.packwiz.installer.ui
 | 
			
		||||
 | 
			
		||||
import javax.swing.SwingWorker
 | 
			
		||||
 | 
			
		||||
// Q: AAA WHAT HAVE YOU DONE THIS IS DISGUSTING
 | 
			
		||||
// A: it just makes things easier, so i can easily have one interface for CLI/GUI
 | 
			
		||||
//    if someone has a better way to do this please PR it
 | 
			
		||||
abstract class SwingWorkerButWithPublicPublish<T, V> : SwingWorker<T, V>() {
 | 
			
		||||
	@SafeVarargs
 | 
			
		||||
	fun publishPublic(vararg chunks: V) {
 | 
			
		||||
		publish(*chunks)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user