mirror of
https://github.com/packwiz/packwiz-installer.git
synced 2025-04-19 21:16:30 +02:00
Port UI to Kotlin
This commit is contained in:
parent
0770029dc6
commit
bead683b7c
@ -77,10 +77,12 @@ if (project.hasProperty("github.token")) {
|
||||
tasks.compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs = listOf("-Xjvm-default=enable")
|
||||
}
|
||||
}
|
||||
tasks.compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs = listOf("-Xjvm-default=enable")
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package link.infra.packwiz.installer.ui;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class CLIHandler implements IUserInterface {
|
||||
|
||||
@Override
|
||||
public void handleException(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(InputStateHandler h) {}
|
||||
|
||||
@Override
|
||||
public void submitProgress(InstallProgress progress) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (progress.hasProgress) {
|
||||
sb.append('(');
|
||||
sb.append(progress.progress);
|
||||
sb.append('/');
|
||||
sb.append(progress.progressTotal);
|
||||
sb.append(") ");
|
||||
}
|
||||
sb.append(progress.message);
|
||||
System.out.println(sb.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeManager(Runnable task) {
|
||||
task.run();
|
||||
System.out.println("Finished successfully!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Boolean> showOptions(List<IOptionDetails> options) {
|
||||
for (IOptionDetails opt : options) {
|
||||
opt.setOptionValue(true);
|
||||
System.out.println("Warning: accepting option " + opt.getName() + " as option choosing is not implemented in the CLI");
|
||||
}
|
||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||
future.complete(false); // Can't be cancelled!
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<IExceptionDetails.ExceptionListResult> showExceptions(List<IExceptionDetails> opts, int numTotal, boolean allowsIgnore) {
|
||||
CompletableFuture<IExceptionDetails.ExceptionListResult> future = new CompletableFuture<>();
|
||||
future.complete(IExceptionDetails.ExceptionListResult.CANCEL);
|
||||
return future;
|
||||
}
|
||||
|
||||
}
|
@ -1,183 +0,0 @@
|
||||
package link.infra.packwiz.installer.ui;
|
||||
|
||||
import link.infra.packwiz.installer.ui.IExceptionDetails.ExceptionListResult;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import java.awt.*;
|
||||
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.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
class ExceptionListWindow extends JDialog {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final JTextArea lblExceptionStacktrace;
|
||||
|
||||
/**
|
||||
* Create the dialog.
|
||||
*/
|
||||
ExceptionListWindow(List<IExceptionDetails> eList, CompletableFuture<ExceptionListResult> future, int numTotal, boolean allowsIgnore, JFrame parentWindow) {
|
||||
super(parentWindow, "Failed file downloads", true);
|
||||
|
||||
setBounds(100, 100, 540, 340);
|
||||
setLocationRelativeTo(parentWindow);
|
||||
getContentPane().setLayout(new BorderLayout());
|
||||
{
|
||||
JPanel errorPanel = new JPanel();
|
||||
getContentPane().add(errorPanel, BorderLayout.NORTH);
|
||||
{
|
||||
JLabel lblWarning = new JLabel("One or more errors were encountered while installing the modpack!");
|
||||
lblWarning.setIcon(UIManager.getIcon("OptionPane.warningIcon"));
|
||||
errorPanel.add(lblWarning);
|
||||
}
|
||||
}
|
||||
JPanel contentPanel = new JPanel();
|
||||
contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||
getContentPane().add(contentPanel, BorderLayout.CENTER);
|
||||
contentPanel.setLayout(new BorderLayout(0, 0));
|
||||
{
|
||||
JSplitPane splitPane = new JSplitPane();
|
||||
splitPane.setResizeWeight(0.3);
|
||||
contentPanel.add(splitPane);
|
||||
{
|
||||
lblExceptionStacktrace = new JTextArea("Select a file");
|
||||
lblExceptionStacktrace.setBackground(UIManager.getColor("List.background"));
|
||||
lblExceptionStacktrace.setOpaque(true);
|
||||
lblExceptionStacktrace.setWrapStyleWord(true);
|
||||
lblExceptionStacktrace.setLineWrap(true);
|
||||
lblExceptionStacktrace.setEditable(false);
|
||||
lblExceptionStacktrace.setFocusable(true);
|
||||
lblExceptionStacktrace.setFont(UIManager.getFont("Label.font"));
|
||||
lblExceptionStacktrace.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||
JScrollPane scrollPane = new JScrollPane(lblExceptionStacktrace);
|
||||
scrollPane.setBorder(new EmptyBorder(0, 0, 0, 0));
|
||||
splitPane.setRightComponent(scrollPane);
|
||||
}
|
||||
{
|
||||
JList<String> list = new JList<>();
|
||||
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
list.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||
ExceptionListModel listModel = new ExceptionListModel(eList);
|
||||
list.setModel(listModel);
|
||||
list.addListSelectionListener(e -> {
|
||||
int i = list.getSelectedIndex();
|
||||
if (i > -1) {
|
||||
StringWriter sw = new StringWriter();
|
||||
listModel.getExceptionAt(i).printStackTrace(new PrintWriter(sw));
|
||||
lblExceptionStacktrace.setText(sw.toString());
|
||||
// Scroll to the top
|
||||
lblExceptionStacktrace.setCaretPosition(0);
|
||||
} else {
|
||||
lblExceptionStacktrace.setText("Select a file");
|
||||
}
|
||||
});
|
||||
JScrollPane scrollPane = new JScrollPane(list);
|
||||
scrollPane.setBorder(new EmptyBorder(0, 0, 0, 0));
|
||||
splitPane.setLeftComponent(scrollPane);
|
||||
}
|
||||
}
|
||||
{
|
||||
JPanel buttonPane = new JPanel();
|
||||
getContentPane().add(buttonPane, BorderLayout.SOUTH);
|
||||
buttonPane.setLayout(new BorderLayout(0, 0));
|
||||
{
|
||||
JPanel rightButtons = new JPanel();
|
||||
buttonPane.add(rightButtons, BorderLayout.EAST);
|
||||
{
|
||||
JButton btnContinue = new JButton("Continue");
|
||||
btnContinue.setToolTipText("Attempt to continue installing, excluding the failed downloads");
|
||||
btnContinue.addActionListener(e -> {
|
||||
future.complete(ExceptionListResult.CONTINUE);
|
||||
ExceptionListWindow.this.dispose();
|
||||
});
|
||||
rightButtons.add(btnContinue);
|
||||
}
|
||||
{
|
||||
JButton btnCancelLaunch = new JButton("Cancel launch");
|
||||
btnCancelLaunch.setToolTipText("Stop launching the game");
|
||||
btnCancelLaunch.addActionListener(e -> {
|
||||
future.complete(ExceptionListResult.CANCEL);
|
||||
ExceptionListWindow.this.dispose();
|
||||
});
|
||||
rightButtons.add(btnCancelLaunch);
|
||||
}
|
||||
{
|
||||
JButton btnIgnoreUpdate = new JButton("Ignore update");
|
||||
btnIgnoreUpdate.setEnabled(allowsIgnore);
|
||||
btnIgnoreUpdate.setToolTipText("Start the game without attempting to update");
|
||||
btnIgnoreUpdate.addActionListener(e -> {
|
||||
future.complete(ExceptionListResult.IGNORE);
|
||||
ExceptionListWindow.this.dispose();
|
||||
});
|
||||
rightButtons.add(btnIgnoreUpdate);
|
||||
{
|
||||
JLabel lblErrored = new JLabel(eList.size() + "/" + numTotal + " errored");
|
||||
lblErrored.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
buttonPane.add(lblErrored, BorderLayout.CENTER);
|
||||
}
|
||||
{
|
||||
JPanel leftButtons = new JPanel();
|
||||
buttonPane.add(leftButtons, BorderLayout.WEST);
|
||||
{
|
||||
JButton btnReportIssue = new JButton("Report issue");
|
||||
boolean supported = Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE);
|
||||
btnReportIssue.setEnabled(supported);
|
||||
if (supported) {
|
||||
btnReportIssue.addActionListener(e -> {
|
||||
try {
|
||||
Desktop.getDesktop().browse(new URI("https://github.com/comp500/packwiz-installer/issues/new"));
|
||||
} catch (IOException | URISyntaxException e1) {
|
||||
// lol the button just won't work i guess
|
||||
}
|
||||
});
|
||||
}
|
||||
leftButtons.add(btnReportIssue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
future.complete(ExceptionListResult.CANCEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowClosed(WindowEvent e) {
|
||||
// Just in case closing didn't get triggered - if something else called dispose() the
|
||||
// future will have already completed
|
||||
future.complete(ExceptionListResult.CANCEL);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class ExceptionListModel extends AbstractListModel<String> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final List<IExceptionDetails> details;
|
||||
|
||||
ExceptionListModel(List<IExceptionDetails> details) {
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return details.size();
|
||||
}
|
||||
|
||||
public String getElementAt(int index) {
|
||||
return details.get(index).getName();
|
||||
}
|
||||
|
||||
Exception getExceptionAt(int index) {
|
||||
return details.get(index).getException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package link.infra.packwiz.installer.ui;
|
||||
|
||||
public interface IExceptionDetails {
|
||||
Exception getException();
|
||||
String getName();
|
||||
|
||||
enum ExceptionListResult {
|
||||
CONTINUE, CANCEL, IGNORE
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package link.infra.packwiz.installer.ui;
|
||||
|
||||
public interface IOptionDetails {
|
||||
String getName();
|
||||
boolean getOptionValue();
|
||||
String getOptionDescription();
|
||||
void setOptionValue(boolean value);
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package link.infra.packwiz.installer.ui;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public interface IUserInterface {
|
||||
|
||||
void show(InputStateHandler handler);
|
||||
|
||||
void handleException(Exception e);
|
||||
|
||||
default void handleExceptionAndExit(Exception e) {
|
||||
handleException(e);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
default void setTitle(String title) {}
|
||||
|
||||
void submitProgress(InstallProgress progress);
|
||||
|
||||
void executeManager(Runnable task);
|
||||
|
||||
// Return true if the installation was cancelled!
|
||||
Future<Boolean> showOptions(List<IOptionDetails> option);
|
||||
|
||||
Future<IExceptionDetails.ExceptionListResult> showExceptions(List<IExceptionDetails> opts, int numTotal, boolean allowsIgnore);
|
||||
|
||||
default void disableOptionsButton() {}
|
||||
|
||||
// Should not return CONTINUE
|
||||
default Future<IExceptionDetails.ExceptionListResult> showCancellationDialog() {
|
||||
CompletableFuture<IExceptionDetails.ExceptionListResult> future = new CompletableFuture<>();
|
||||
future.complete(IExceptionDetails.ExceptionListResult.CANCEL);
|
||||
return future;
|
||||
}
|
||||
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package link.infra.packwiz.installer.ui;
|
||||
|
||||
public class InputStateHandler {
|
||||
private boolean optionsButtonPressed = false;
|
||||
private boolean cancelButtonPressed = false;
|
||||
|
||||
synchronized void pressCancelButton() {
|
||||
this.cancelButtonPressed = true;
|
||||
}
|
||||
|
||||
synchronized void pressOptionsButton() {
|
||||
this.optionsButtonPressed = true;
|
||||
}
|
||||
|
||||
public synchronized boolean getCancelButton() {
|
||||
return cancelButtonPressed;
|
||||
}
|
||||
|
||||
public synchronized boolean getOptionsButton() {
|
||||
return optionsButtonPressed;
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package link.infra.packwiz.installer.ui;
|
||||
|
||||
public class InstallProgress {
|
||||
public final String message;
|
||||
public final boolean hasProgress;
|
||||
public final int progress;
|
||||
public final int progressTotal;
|
||||
|
||||
public InstallProgress(String message) {
|
||||
this.message = message;
|
||||
hasProgress = false;
|
||||
progress = 0;
|
||||
progressTotal = 0;
|
||||
}
|
||||
|
||||
public InstallProgress(String message, int progress, int progressTotal) {
|
||||
this.message = message;
|
||||
hasProgress = true;
|
||||
this.progress = progress;
|
||||
this.progressTotal = progressTotal;
|
||||
}
|
||||
}
|
@ -1,228 +0,0 @@
|
||||
package link.infra.packwiz.installer.ui;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class InstallWindow implements IUserInterface {
|
||||
|
||||
private JFrame frmPackwizlauncher;
|
||||
private JLabel lblProgresslabel;
|
||||
private JProgressBar progressBar;
|
||||
private InputStateHandler inputStateHandler;
|
||||
|
||||
private String title = "Updating modpack...";
|
||||
private SwingWorkerButWithPublicPublish<Void, InstallProgress> worker;
|
||||
private AtomicBoolean aboutToCrash = new AtomicBoolean();
|
||||
private JButton btnOptions;
|
||||
|
||||
@Override
|
||||
public void show(InputStateHandler handler) {
|
||||
this.inputStateHandler = handler;
|
||||
EventQueue.invokeLater(() -> {
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
initialize();
|
||||
frmPackwizlauncher.setVisible(true);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the contents of the frame.
|
||||
* @wbp.parser.entryPoint
|
||||
*/
|
||||
private void initialize() {
|
||||
frmPackwizlauncher = new JFrame();
|
||||
frmPackwizlauncher.setTitle(title);
|
||||
frmPackwizlauncher.setBounds(100, 100, 493, 95);
|
||||
frmPackwizlauncher.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frmPackwizlauncher.setLocationRelativeTo(null);
|
||||
|
||||
JPanel panel = new JPanel();
|
||||
panel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||
frmPackwizlauncher.getContentPane().add(panel, BorderLayout.CENTER);
|
||||
panel.setLayout(new BorderLayout(0, 0));
|
||||
|
||||
progressBar = new JProgressBar();
|
||||
progressBar.setIndeterminate(true);
|
||||
panel.add(progressBar, BorderLayout.CENTER);
|
||||
|
||||
lblProgresslabel = new JLabel("Loading...");
|
||||
panel.add(lblProgresslabel, BorderLayout.SOUTH);
|
||||
|
||||
JPanel panel_1 = new JPanel();
|
||||
panel_1.setBorder(new EmptyBorder(0, 5, 0, 5));
|
||||
frmPackwizlauncher.getContentPane().add(panel_1, BorderLayout.EAST);
|
||||
GridBagLayout gbl_panel_1 = new GridBagLayout();
|
||||
panel_1.setLayout(gbl_panel_1);
|
||||
|
||||
btnOptions = new JButton("Optional mods...");
|
||||
btnOptions.addActionListener(e -> {
|
||||
btnOptions.setText("Loading...");
|
||||
btnOptions.setEnabled(false);
|
||||
inputStateHandler.pressOptionsButton();
|
||||
});
|
||||
btnOptions.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
GridBagConstraints gbc_btnOptions = new GridBagConstraints();
|
||||
gbc_btnOptions.gridx = 0;
|
||||
gbc_btnOptions.gridy = 0;
|
||||
panel_1.add(btnOptions, gbc_btnOptions);
|
||||
|
||||
JButton btnCancel = new JButton("Cancel");
|
||||
btnCancel.addActionListener(e -> {
|
||||
btnCancel.setEnabled(false);
|
||||
inputStateHandler.pressCancelButton();
|
||||
});
|
||||
btnCancel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
GridBagConstraints gbc_btnCancel = new GridBagConstraints();
|
||||
gbc_btnCancel.gridx = 0;
|
||||
gbc_btnCancel.gridy = 1;
|
||||
panel_1.add(btnCancel, gbc_btnCancel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleException(Exception e) {
|
||||
e.printStackTrace();
|
||||
EventQueue.invokeLater(() -> {
|
||||
JOptionPane.showMessageDialog(null, "An error occurred: \n" + e.getClass().getCanonicalName() + ": " + e.getMessage(), title, JOptionPane.ERROR_MESSAGE);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleExceptionAndExit(Exception e) {
|
||||
e.printStackTrace();
|
||||
// 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.getClass().getCanonicalName() + ": " + e.getMessage(), title, JOptionPane.ERROR_MESSAGE);
|
||||
System.exit(1);
|
||||
});
|
||||
// Pause forever, so it blocks while we wait for System.exit to take effect
|
||||
try {
|
||||
Thread.currentThread().join();
|
||||
} catch (InterruptedException ex) {
|
||||
// no u
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
if (frmPackwizlauncher != null) {
|
||||
EventQueue.invokeLater(() -> frmPackwizlauncher.setTitle(title));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submitProgress(InstallProgress progress) {
|
||||
StringBuilder sb = new 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?
|
||||
System.out.println(sb.toString());
|
||||
if (worker != null) {
|
||||
worker.publishPublic(progress);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeManager(Runnable task) {
|
||||
EventQueue.invokeLater(() -> {
|
||||
worker = new SwingWorkerButWithPublicPublish<Void, InstallProgress>() {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() {
|
||||
task.run();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void process(List<InstallProgress> chunks) {
|
||||
// Only process last chunk
|
||||
if (chunks.size() > 0) {
|
||||
InstallProgress prog = chunks.get(chunks.size() - 1);
|
||||
if (prog.hasProgress) {
|
||||
progressBar.setIndeterminate(false);
|
||||
progressBar.setValue(prog.progress);
|
||||
progressBar.setMaximum(prog.progressTotal);
|
||||
} else {
|
||||
progressBar.setIndeterminate(true);
|
||||
progressBar.setValue(0);
|
||||
}
|
||||
lblProgresslabel.setText(prog.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
if (aboutToCrash.get()) {
|
||||
return;
|
||||
}
|
||||
// TODO: a better way to do this?
|
||||
frmPackwizlauncher.dispose();
|
||||
System.out.println("Finished successfully!");
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
};
|
||||
worker.execute();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Boolean> showOptions(List<IOptionDetails> opts) {
|
||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||
EventQueue.invokeLater(() -> {
|
||||
OptionsSelectWindow dialog = new OptionsSelectWindow(opts, future, frmPackwizlauncher);
|
||||
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
|
||||
dialog.setVisible(true);
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<IExceptionDetails.ExceptionListResult> showExceptions(List<IExceptionDetails> opts, int numTotal, boolean allowsIgnore) {
|
||||
CompletableFuture<IExceptionDetails.ExceptionListResult> future = new CompletableFuture<>();
|
||||
EventQueue.invokeLater(() -> {
|
||||
ExceptionListWindow dialog = new ExceptionListWindow(opts, future, numTotal, allowsIgnore, frmPackwizlauncher);
|
||||
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
|
||||
dialog.setVisible(true);
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableOptionsButton() {
|
||||
if (btnOptions != null) {
|
||||
btnOptions.setText("Optional mods...");
|
||||
btnOptions.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<IExceptionDetails.ExceptionListResult> showCancellationDialog() {
|
||||
CompletableFuture<IExceptionDetails.ExceptionListResult> future = new CompletableFuture<>();
|
||||
EventQueue.invokeLater(() -> {
|
||||
Object[] buttons = {"Quit", "Ignore"};
|
||||
int 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(result == 0 ? IExceptionDetails.ExceptionListResult.CANCEL : IExceptionDetails.ExceptionListResult.IGNORE);
|
||||
});
|
||||
return future;
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package link.infra.packwiz.installer.ui;
|
||||
|
||||
// Serves as a proxy for IOptionDetails, so that setOptionValue isn't called until OK is clicked
|
||||
class OptionTempHandler implements IOptionDetails {
|
||||
private final IOptionDetails opt;
|
||||
private boolean tempValue;
|
||||
|
||||
OptionTempHandler(IOptionDetails opt) {
|
||||
this.opt = opt;
|
||||
tempValue = opt.getOptionValue();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return opt.getName();
|
||||
}
|
||||
|
||||
public String getOptionDescription() {
|
||||
return opt.getOptionDescription();
|
||||
}
|
||||
|
||||
public boolean getOptionValue() {
|
||||
return tempValue;
|
||||
}
|
||||
|
||||
public void setOptionValue(boolean value) {
|
||||
tempValue = value;
|
||||
}
|
||||
|
||||
void finalise() {
|
||||
opt.setOptionValue(tempValue);
|
||||
}
|
||||
|
||||
}
|
@ -1,205 +0,0 @@
|
||||
package link.infra.packwiz.installer.ui;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.swing.event.TableModelListener;
|
||||
import javax.swing.table.TableModel;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class OptionsSelectWindow extends JDialog implements ActionListener {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final JTextArea lblOptionDescription;
|
||||
private final OptionTableModel tableModel;
|
||||
private final CompletableFuture<Boolean> future;
|
||||
|
||||
/**
|
||||
* Create the dialog.
|
||||
*/
|
||||
OptionsSelectWindow(List<IOptionDetails> optList, CompletableFuture<Boolean> future, JFrame parentWindow) {
|
||||
super(parentWindow, "Select optional mods...", true);
|
||||
|
||||
tableModel = new OptionTableModel(optList);
|
||||
this.future = future;
|
||||
|
||||
setBounds(100, 100, 450, 300);
|
||||
setLocationRelativeTo(parentWindow);
|
||||
getContentPane().setLayout(new BorderLayout());
|
||||
JPanel contentPanel = new JPanel();
|
||||
contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||
getContentPane().add(contentPanel, BorderLayout.CENTER);
|
||||
contentPanel.setLayout(new BorderLayout(0, 0));
|
||||
{
|
||||
JSplitPane splitPane = new JSplitPane();
|
||||
splitPane.setResizeWeight(0.5);
|
||||
contentPanel.add(splitPane);
|
||||
{
|
||||
JTable table = new JTable();
|
||||
table.setShowVerticalLines(false);
|
||||
table.setShowHorizontalLines(false);
|
||||
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
table.setShowGrid(false);
|
||||
table.setModel(tableModel);
|
||||
table.getColumnModel().getColumn(0).setResizable(false);
|
||||
table.getColumnModel().getColumn(0).setPreferredWidth(15);
|
||||
table.getColumnModel().getColumn(0).setMaxWidth(15);
|
||||
table.getColumnModel().getColumn(1).setResizable(false);
|
||||
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
int i = table.getSelectedRow();
|
||||
if (i > -1) {
|
||||
lblOptionDescription.setText(tableModel.getDescription(i));
|
||||
} else {
|
||||
lblOptionDescription.setText("Select an option...");
|
||||
}
|
||||
}
|
||||
});
|
||||
table.setTableHeader(null);
|
||||
JScrollPane scrollPane = new JScrollPane(table);
|
||||
scrollPane.getViewport().setBackground(UIManager.getColor("List.background"));
|
||||
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
scrollPane.setBorder(new EmptyBorder(0, 0, 0, 0));
|
||||
splitPane.setLeftComponent(scrollPane);
|
||||
}
|
||||
{
|
||||
lblOptionDescription = new JTextArea("Select an option...");
|
||||
lblOptionDescription.setBackground(UIManager.getColor("List.background"));
|
||||
lblOptionDescription.setOpaque(true);
|
||||
lblOptionDescription.setWrapStyleWord(true);
|
||||
lblOptionDescription.setLineWrap(true);
|
||||
lblOptionDescription.setEditable(false);
|
||||
lblOptionDescription.setFocusable(false);
|
||||
lblOptionDescription.setFont(UIManager.getFont("Label.font"));
|
||||
lblOptionDescription.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||
JScrollPane scrollPane = new JScrollPane(lblOptionDescription);
|
||||
scrollPane.setBorder(new EmptyBorder(0, 0, 0, 0));
|
||||
splitPane.setRightComponent(scrollPane);
|
||||
}
|
||||
}
|
||||
{
|
||||
JPanel buttonPane = new JPanel();
|
||||
buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
|
||||
getContentPane().add(buttonPane, BorderLayout.SOUTH);
|
||||
{
|
||||
JButton okButton = new JButton("OK");
|
||||
okButton.setActionCommand("OK");
|
||||
okButton.addActionListener(this);
|
||||
buttonPane.add(okButton);
|
||||
getRootPane().setDefaultButton(okButton);
|
||||
}
|
||||
{
|
||||
JButton cancelButton = new JButton("Cancel");
|
||||
cancelButton.setActionCommand("Cancel");
|
||||
cancelButton.addActionListener(this);
|
||||
buttonPane.add(cancelButton);
|
||||
}
|
||||
}
|
||||
addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
future.complete(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowClosed(WindowEvent e) {
|
||||
// Just in case closing didn't get triggered - if something else called dispose() the
|
||||
// future will have already completed
|
||||
future.complete(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class OptionTableModel implements TableModel {
|
||||
private List<OptionTempHandler> opts = new ArrayList<>();
|
||||
|
||||
OptionTableModel(List<IOptionDetails> givenOpts) {
|
||||
for (IOptionDetails opt : givenOpts) {
|
||||
opts.add(new OptionTempHandler(opt));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return opts.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
private final String[] columnNames = {"Enabled", "Mod name"};
|
||||
private final Class<?>[] columnTypes = {Boolean.class, String.class};
|
||||
private final boolean[] columnEditables = {true, false};
|
||||
|
||||
@Override
|
||||
public String getColumnName(int columnIndex) {
|
||||
return columnNames[columnIndex];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass(int columnIndex) {
|
||||
return columnTypes[columnIndex];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||
return columnEditables[columnIndex];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
OptionTempHandler opt = opts.get(rowIndex);
|
||||
return columnIndex == 0 ? opt.getOptionValue() : opt.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
||||
if (columnIndex == 0) {
|
||||
OptionTempHandler opt = opts.get(rowIndex);
|
||||
opt.setOptionValue((boolean) aValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Noop, the table model doesn't change!
|
||||
@Override
|
||||
public void addTableModelListener(TableModelListener l) {}
|
||||
|
||||
@Override
|
||||
public void removeTableModelListener(TableModelListener l) {}
|
||||
|
||||
String getDescription(int rowIndex) {
|
||||
return opts.get(rowIndex).getOptionDescription();
|
||||
}
|
||||
|
||||
void finalise() {
|
||||
for (OptionTempHandler opt : opts) {
|
||||
opt.finalise();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (e.getActionCommand().equals("OK")) {
|
||||
tableModel.finalise();
|
||||
future.complete(false);
|
||||
dispose();
|
||||
} else if (e.getActionCommand().equals("Cancel")) {
|
||||
future.complete(true);
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
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
|
||||
public abstract class SwingWorkerButWithPublicPublish<T,V> extends SwingWorker<T,V> {
|
||||
@SafeVarargs
|
||||
public final void publishPublic(V... chunks) {
|
||||
publish(chunks);
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user