diff --git a/build.gradle.kts b/build.gradle.kts index 40e762a..ad4d916 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") } } \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/ui/CLIHandler.java b/src/main/java/link/infra/packwiz/installer/ui/CLIHandler.java deleted file mode 100644 index e9973cd..0000000 --- a/src/main/java/link/infra/packwiz/installer/ui/CLIHandler.java +++ /dev/null @@ -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 showOptions(List 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 future = new CompletableFuture<>(); - future.complete(false); // Can't be cancelled! - return future; - } - - @Override - public Future showExceptions(List opts, int numTotal, boolean allowsIgnore) { - CompletableFuture future = new CompletableFuture<>(); - future.complete(IExceptionDetails.ExceptionListResult.CANCEL); - return future; - } - -} diff --git a/src/main/java/link/infra/packwiz/installer/ui/ExceptionListWindow.java b/src/main/java/link/infra/packwiz/installer/ui/ExceptionListWindow.java deleted file mode 100644 index 126e5a1..0000000 --- a/src/main/java/link/infra/packwiz/installer/ui/ExceptionListWindow.java +++ /dev/null @@ -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 eList, CompletableFuture 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 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 { - private static final long serialVersionUID = 1L; - private final List details; - - ExceptionListModel(List 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(); - } - } - -} diff --git a/src/main/java/link/infra/packwiz/installer/ui/IExceptionDetails.java b/src/main/java/link/infra/packwiz/installer/ui/IExceptionDetails.java deleted file mode 100644 index 7207f8b..0000000 --- a/src/main/java/link/infra/packwiz/installer/ui/IExceptionDetails.java +++ /dev/null @@ -1,10 +0,0 @@ -package link.infra.packwiz.installer.ui; - -public interface IExceptionDetails { - Exception getException(); - String getName(); - - enum ExceptionListResult { - CONTINUE, CANCEL, IGNORE - } -} diff --git a/src/main/java/link/infra/packwiz/installer/ui/IOptionDetails.java b/src/main/java/link/infra/packwiz/installer/ui/IOptionDetails.java deleted file mode 100644 index 1e672a1..0000000 --- a/src/main/java/link/infra/packwiz/installer/ui/IOptionDetails.java +++ /dev/null @@ -1,8 +0,0 @@ -package link.infra.packwiz.installer.ui; - -public interface IOptionDetails { - String getName(); - boolean getOptionValue(); - String getOptionDescription(); - void setOptionValue(boolean value); -} diff --git a/src/main/java/link/infra/packwiz/installer/ui/IUserInterface.java b/src/main/java/link/infra/packwiz/installer/ui/IUserInterface.java deleted file mode 100644 index 104ad80..0000000 --- a/src/main/java/link/infra/packwiz/installer/ui/IUserInterface.java +++ /dev/null @@ -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 showOptions(List option); - - Future showExceptions(List opts, int numTotal, boolean allowsIgnore); - - default void disableOptionsButton() {} - - // Should not return CONTINUE - default Future showCancellationDialog() { - CompletableFuture future = new CompletableFuture<>(); - future.complete(IExceptionDetails.ExceptionListResult.CANCEL); - return future; - } - -} diff --git a/src/main/java/link/infra/packwiz/installer/ui/InputStateHandler.java b/src/main/java/link/infra/packwiz/installer/ui/InputStateHandler.java deleted file mode 100644 index ee63d45..0000000 --- a/src/main/java/link/infra/packwiz/installer/ui/InputStateHandler.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/link/infra/packwiz/installer/ui/InstallProgress.java b/src/main/java/link/infra/packwiz/installer/ui/InstallProgress.java deleted file mode 100644 index 6526096..0000000 --- a/src/main/java/link/infra/packwiz/installer/ui/InstallProgress.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/ui/InstallWindow.java b/src/main/java/link/infra/packwiz/installer/ui/InstallWindow.java deleted file mode 100644 index 7c70a1f..0000000 --- a/src/main/java/link/infra/packwiz/installer/ui/InstallWindow.java +++ /dev/null @@ -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 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() { - - @Override - protected Void doInBackground() { - task.run(); - return null; - } - - @Override - protected void process(List 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 showOptions(List opts) { - CompletableFuture 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 showExceptions(List opts, int numTotal, boolean allowsIgnore) { - CompletableFuture 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 showCancellationDialog() { - CompletableFuture 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; - } -} diff --git a/src/main/java/link/infra/packwiz/installer/ui/OptionTempHandler.java b/src/main/java/link/infra/packwiz/installer/ui/OptionTempHandler.java deleted file mode 100644 index 67d92e0..0000000 --- a/src/main/java/link/infra/packwiz/installer/ui/OptionTempHandler.java +++ /dev/null @@ -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); - } - -} diff --git a/src/main/java/link/infra/packwiz/installer/ui/OptionsSelectWindow.java b/src/main/java/link/infra/packwiz/installer/ui/OptionsSelectWindow.java deleted file mode 100644 index 392f5ba..0000000 --- a/src/main/java/link/infra/packwiz/installer/ui/OptionsSelectWindow.java +++ /dev/null @@ -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 future; - - /** - * Create the dialog. - */ - OptionsSelectWindow(List optList, CompletableFuture 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 opts = new ArrayList<>(); - - OptionTableModel(List 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(); - } - } - -} diff --git a/src/main/java/link/infra/packwiz/installer/ui/SwingWorkerButWithPublicPublish.java b/src/main/java/link/infra/packwiz/installer/ui/SwingWorkerButWithPublicPublish.java deleted file mode 100644 index a5c3f26..0000000 --- a/src/main/java/link/infra/packwiz/installer/ui/SwingWorkerButWithPublicPublish.java +++ /dev/null @@ -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 extends SwingWorker { - @SafeVarargs - public final void publishPublic(V... chunks) { - publish(chunks); - } -} \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/CLIHandler.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/CLIHandler.kt new file mode 100644 index 0000000..4564624 --- /dev/null +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/CLIHandler.kt @@ -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): Future { + 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().apply { + complete(false) // Can't be cancelled! + } + } + + override fun showExceptions(exceptions: List, numTotal: Int, allowsIgnore: Boolean): Future { + val future = CompletableFuture() + future.complete(ExceptionListResult.CANCEL) + return future + } +} \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/ExceptionListWindow.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/ExceptionListWindow.kt new file mode 100644 index 0000000..094844e --- /dev/null +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/ExceptionListWindow.kt @@ -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, future: CompletableFuture, 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) : AbstractListModel() { + 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().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) + } + }) + } +} \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/IExceptionDetails.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/IExceptionDetails.kt new file mode 100644 index 0000000..a092ad6 --- /dev/null +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/IExceptionDetails.kt @@ -0,0 +1,10 @@ +package link.infra.packwiz.installer.ui + +interface IExceptionDetails { + val exception: Exception + val name: String + + enum class ExceptionListResult { + CONTINUE, CANCEL, IGNORE + } +} \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/IOptionDetails.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/IOptionDetails.kt new file mode 100644 index 0000000..3665b6b --- /dev/null +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/IOptionDetails.kt @@ -0,0 +1,7 @@ +package link.infra.packwiz.installer.ui + +interface IOptionDetails { + val name: String + var optionValue: Boolean + val optionDescription: String +} \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/IUserInterface.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/IUserInterface.kt new file mode 100644 index 0000000..1c37a67 --- /dev/null +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/IUserInterface.kt @@ -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): Future + + fun showExceptions(exceptions: List, numTotal: Int, allowsIgnore: Boolean): Future + @JvmDefault + fun disableOptionsButton() {} + // Should not return CONTINUE + @JvmDefault + fun showCancellationDialog(): Future { + return CompletableFuture().apply { + complete(ExceptionListResult.CANCEL) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/InputStateHandler.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/InputStateHandler.kt new file mode 100644 index 0000000..75a517e --- /dev/null +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/InputStateHandler.kt @@ -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 + } +} \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/InstallProgress.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/InstallProgress.kt new file mode 100644 index 0000000..1817c07 --- /dev/null +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/InstallProgress.kt @@ -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) +} \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/InstallWindow.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/InstallWindow.kt new file mode 100644 index 0000000..e4aea73 --- /dev/null +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/InstallWindow.kt @@ -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? = 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) { + EventQueue.invokeLater { + // TODO: rewrite this stupidity to use channels??!!! + worker = object : SwingWorkerButWithPublicPublish() { + override fun doInBackground() { + task.invoke() + } + + override fun process(chunks: List) { + // 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): Future { + val future = CompletableFuture() + EventQueue.invokeLater { + OptionsSelectWindow(options, future, frmPackwizlauncher).apply { + defaultCloseOperation = JDialog.DISPOSE_ON_CLOSE + isVisible = true + } + } + return future + } + + override fun showExceptions(exceptions: List, numTotal: Int, allowsIgnore: Boolean): Future { + val future = CompletableFuture() + 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 { + val future = CompletableFuture() + 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 + } +} \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/OptionTempHandler.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/OptionTempHandler.kt new file mode 100644 index 0000000..9c29304 --- /dev/null +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/OptionTempHandler.kt @@ -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 + } +} \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/OptionsSelectWindow.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/OptionsSelectWindow.kt new file mode 100644 index 0000000..5d24ee1 --- /dev/null +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/OptionsSelectWindow.kt @@ -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, future: CompletableFuture, parentWindow: JFrame?) : JDialog(parentWindow, "Select optional mods...", true), ActionListener { + private val lblOptionDescription: JTextArea + private val tableModel: OptionTableModel + private val future: CompletableFuture + + private class OptionTableModel internal constructor(givenOpts: List) : TableModel { + private val opts: List + + init { + val mutOpts = ArrayList() + 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) + } + }) + } +} \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/SwingWorkerButWithPublicPublish.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/SwingWorkerButWithPublicPublish.kt new file mode 100644 index 0000000..fc67839 --- /dev/null +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/SwingWorkerButWithPublicPublish.kt @@ -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 : SwingWorker() { + @SafeVarargs + fun publishPublic(vararg chunks: V) { + publish(*chunks) + } +} \ No newline at end of file