summaryrefslogtreecommitdiff
path: root/base/src/main/java/bjc/utils/gui
diff options
context:
space:
mode:
Diffstat (limited to 'base/src/main/java/bjc/utils/gui')
-rw-r--r--base/src/main/java/bjc/utils/gui/ExtensionFileFilter.java56
-rw-r--r--base/src/main/java/bjc/utils/gui/SimpleDialogs.java269
-rw-r--r--base/src/main/java/bjc/utils/gui/SimpleFileChooser.java198
-rw-r--r--base/src/main/java/bjc/utils/gui/SimpleInternalDialogs.java208
-rw-r--r--base/src/main/java/bjc/utils/gui/SimpleInternalFrame.java40
-rw-r--r--base/src/main/java/bjc/utils/gui/SimpleJList.java49
-rw-r--r--base/src/main/java/bjc/utils/gui/SimpleTitledBorder.java25
-rw-r--r--base/src/main/java/bjc/utils/gui/TextAreaOutputStream.java35
-rw-r--r--base/src/main/java/bjc/utils/gui/awt/ExtensionFileFilter.java50
-rw-r--r--base/src/main/java/bjc/utils/gui/awt/SimpleFileDialog.java144
-rw-r--r--base/src/main/java/bjc/utils/gui/layout/AutosizeLayout.java22
-rw-r--r--base/src/main/java/bjc/utils/gui/layout/HLayout.java25
-rw-r--r--base/src/main/java/bjc/utils/gui/layout/VLayout.java25
-rw-r--r--base/src/main/java/bjc/utils/gui/panels/DropdownListPanel.java73
-rw-r--r--base/src/main/java/bjc/utils/gui/panels/FormattedInputPanel.java66
-rw-r--r--base/src/main/java/bjc/utils/gui/panels/HolderOutputPanel.java79
-rw-r--r--base/src/main/java/bjc/utils/gui/panels/ListParameterPanel.java133
-rw-r--r--base/src/main/java/bjc/utils/gui/panels/SimpleInputPanel.java45
-rw-r--r--base/src/main/java/bjc/utils/gui/panels/SimpleListPanel.java93
-rw-r--r--base/src/main/java/bjc/utils/gui/panels/SimpleSpinnerPanel.java42
-rw-r--r--base/src/main/java/bjc/utils/gui/panels/SliderInputPanel.java187
-rw-r--r--base/src/main/java/bjc/utils/gui/panels/package-info.java5
22 files changed, 1869 insertions, 0 deletions
diff --git a/base/src/main/java/bjc/utils/gui/ExtensionFileFilter.java b/base/src/main/java/bjc/utils/gui/ExtensionFileFilter.java
new file mode 100644
index 0000000..7c487eb
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/ExtensionFileFilter.java
@@ -0,0 +1,56 @@
+package bjc.utils.gui;
+
+import java.io.File;
+import java.util.List;
+
+import javax.swing.filechooser.FileFilter;
+
+import bjc.utils.funcdata.FunctionalList;
+import bjc.utils.funcdata.IList;
+
+/**
+ * A file filter based on extensions.
+ *
+ * Built for Swing.
+ *
+ * @author ben
+ *
+ */
+public class ExtensionFileFilter extends FileFilter {
+ /**
+ * The list holding all filtered extensions
+ */
+ private final IList<String> extensions;
+
+ /**
+ * Create a new filter only showing files with the specified extensions.
+ *
+ * @param exts
+ * The extensions to show in this filter.
+ */
+ public ExtensionFileFilter(final List<String> exts) {
+ extensions = new FunctionalList<>(exts);
+ }
+
+ /**
+ * Create a new filter only showing files with the specified extensions.
+ *
+ * @param exts
+ * The extensions to show in this filter.
+ */
+ public ExtensionFileFilter(final String... exts) {
+ extensions = new FunctionalList<>(exts);
+ }
+
+ @Override
+ public boolean accept(final File pathname) {
+ if (pathname == null) throw new NullPointerException("Pathname must not be null");
+
+ return extensions.anyMatch(pathname.getName()::endsWith);
+ }
+
+ @Override
+ public String getDescription() {
+ return extensions.toString();
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/bjc/utils/gui/SimpleDialogs.java b/base/src/main/java/bjc/utils/gui/SimpleDialogs.java
new file mode 100644
index 0000000..59eb1c3
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/SimpleDialogs.java
@@ -0,0 +1,269 @@
+package bjc.utils.gui;
+
+import java.awt.Component;
+import java.awt.Frame;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import bjc.utils.gui.layout.VLayout;
+
+/**
+ * Utility class for getting simple input from the user.
+ *
+ * @author ben
+ *
+ */
+public class SimpleDialogs {
+ /**
+ * Get a bounded integer from the user.
+ *
+ * @param parent
+ * The parent component for the dialogs.
+ * @param title
+ * The title for the dialogs.
+ * @param prompt
+ * The prompt to tell the user what to enter.
+ * @param lowerBound
+ * The lower integer bound to accept.
+ * @param upperBound
+ * The upper integer bound to accept.
+ * @return A int within the specified bounds.
+ */
+ public static int getBoundedInt(final Component parent, final String title, final String prompt,
+ final int lowerBound, final int upperBound) {
+ return getValue(parent, title, prompt, (strang) -> {
+ try {
+ final int value = Integer.parseInt(strang);
+
+ return value < upperBound && value > lowerBound;
+ } catch (final NumberFormatException nfex) {
+ // We don't care about the specifics of the
+ // exception, just
+ // that this value isn't good
+ return false;
+ }
+ }, Integer::parseInt);
+ }
+
+ /**
+ * Asks the user to pick an option from a series of choices.
+ *
+ * @param <E>
+ * The type of choices for the user to pick
+ *
+ * @param parent
+ * The parent frame for this dialog
+ * @param title
+ * The title of this dialog
+ * @param question
+ * The question being asked
+ * @param choices
+ * The available choices for the question
+ * @return The choice the user picked, or null if they didn't pick one
+ */
+ @SuppressWarnings("unchecked")
+ public static <E> E getChoice(final Frame parent, final String title, final String question,
+ final E... choices) {
+ if (parent == null)
+ throw new NullPointerException("Parent must not be null");
+ else if (title == null)
+ throw new NullPointerException("Title must not be null");
+ else if (question == null) throw new NullPointerException("Question must not be null");
+
+ final JDialog chooser = new JDialog(parent, title, true);
+ chooser.setLayout(new VLayout(2));
+
+ final JPanel questionPane = new JPanel();
+
+ final JLabel questionText = new JLabel(question);
+ final JComboBox<E> questionChoices = new JComboBox<>(choices);
+
+ questionPane.add(questionText);
+ questionPane.add(questionChoices);
+
+ final JPanel buttonPane = new JPanel();
+
+ final JButton okButton = new JButton("Ok");
+ final JButton cancelButton = new JButton("Cancel");
+
+ okButton.addActionListener((event) -> chooser.dispose());
+ cancelButton.addActionListener((event) -> chooser.dispose());
+
+ buttonPane.add(cancelButton);
+ buttonPane.add(okButton);
+
+ chooser.add(questionPane);
+ chooser.add(buttonPane);
+
+ chooser.pack();
+ chooser.setVisible(true);
+
+ return (E) questionChoices.getSelectedItem();
+ }
+
+ /**
+ * Get a integer from the user
+ *
+ * @param parent
+ * The parent component for dialogs.
+ * @param title
+ * The title for dialogs.
+ * @param prompt
+ * The prompt to tell the user what to enter.
+ * @return A int.
+ */
+ public static int getInt(final Component parent, final String title, final String prompt) {
+ return getValue(parent, title, prompt, strang -> {
+ try {
+ Integer.parseInt(strang);
+ return true;
+ } catch (final NumberFormatException nfex) {
+ // We don't care about this exception, just mark
+ // the value
+ // as not good
+ return false;
+ }
+ }, Integer::parseInt);
+ }
+
+ /**
+ * Get a string from the user
+ *
+ * @param parent
+ * The parent component for dialogs.
+ * @param title
+ * The title for the dialogs.
+ * @param prompt
+ * The prompt to tell the user what to enter.
+ * @return A string.
+ */
+ public static String getString(final Component parent, final String title, final String prompt) {
+ if (parent == null)
+ throw new NullPointerException("Parent must not be null");
+ else if (title == null)
+ throw new NullPointerException("Title must not be null");
+ else if (prompt == null) throw new NullPointerException("Prompt must not be null");
+
+ return JOptionPane.showInputDialog(parent, prompt, title, JOptionPane.QUESTION_MESSAGE);
+ }
+
+ /**
+ * Get a value parsable from a string from the user.
+ *
+ * @param <E>
+ * The type of the value parsed from the string
+ *
+ * @param parent
+ * The parent component for dialogs.
+ * @param title
+ * The title for dialogs.
+ * @param prompt
+ * The prompt to tell the user what to enter.
+ * @param validator
+ * A predicate to determine if a input is valid.
+ * @param transformer
+ * The function to transform the string into a value.
+ * @return The value parsed from a string.
+ */
+ public static <E> E getValue(final Component parent, final String title, final String prompt,
+ final Predicate<String> validator, final Function<String, E> transformer) {
+ if (validator == null)
+ throw new NullPointerException("Validator must not be null");
+ else if (transformer == null) throw new NullPointerException("Transformer must not be null");
+
+ String input = getString(parent, title, prompt);
+
+ while (!validator.test(input)) {
+ showError(parent, "I/O Error", "Please enter a valid value");
+
+ input = getString(parent, title, prompt);
+ }
+
+ return transformer.apply(input);
+ }
+
+ /**
+ * Get a whole number from the user.
+ *
+ * @param parent
+ * The parent component for dialogs.
+ * @param title
+ * The title for dialogs.
+ * @param prompt
+ * The prompt to tell the user what to enter.
+ * @return A whole number.
+ */
+ public static int getWhole(final Component parent, final String title, final String prompt) {
+ return getBoundedInt(parent, title, prompt, 0, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Ask the user a Yes/No question.
+ *
+ * @param parent
+ * The parent component for dialogs.
+ * @param title
+ * The title for dialogs.
+ * @param question
+ * The question to ask the user.
+ * @return True if the user said yes, false otherwise.
+ */
+ public static boolean getYesNo(final Component parent, final String title, final String question) {
+ if (parent == null)
+ throw new NullPointerException("Parent must not be null");
+ else if (title == null)
+ throw new NullPointerException("Title must not be null");
+ else if (question == null) throw new NullPointerException("Question must not be null");
+
+ final int result = JOptionPane.showConfirmDialog(parent, question, title, JOptionPane.YES_NO_OPTION);
+
+ return result == JOptionPane.YES_OPTION ? true : false;
+ }
+
+ /**
+ * Show a error message to the user
+ *
+ * @param parent
+ * The parent component for dialogs.
+ * @param title
+ * The title for dialogs.
+ * @param message
+ * The error to show the user.
+ */
+ public static void showError(final Component parent, final String title, final String message) {
+ if (parent == null)
+ throw new NullPointerException("Parent must not be null");
+ else if (title == null)
+ throw new NullPointerException("Title must not be null");
+ else if (message == null) throw new NullPointerException("Error message must not be null");
+
+ JOptionPane.showMessageDialog(parent, message, title, JOptionPane.ERROR_MESSAGE);
+ }
+
+ /**
+ * Show an informative message to the user
+ *
+ * @param parent
+ * The parent for this dialog
+ * @param title
+ * Show the title for this dialog
+ * @param message
+ * Show the message for this dialog
+ */
+ public static void showMessage(final Component parent, final String title, final String message) {
+ if (parent == null)
+ throw new NullPointerException("Parent must not be null");
+ else if (title == null)
+ throw new NullPointerException("Title must not be null");
+ else if (message == null) throw new NullPointerException("Message must not be null");
+
+ JOptionPane.showMessageDialog(parent, title, message, JOptionPane.INFORMATION_MESSAGE);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/SimpleFileChooser.java b/base/src/main/java/bjc/utils/gui/SimpleFileChooser.java
new file mode 100644
index 0000000..7da0bd8
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/SimpleFileChooser.java
@@ -0,0 +1,198 @@
+package bjc.utils.gui;
+
+import java.awt.Component;
+import java.io.File;
+
+import javax.swing.JFileChooser;
+
+import bjc.utils.exceptions.FileNotChosenException;
+
+/**
+ * Utility class for easily prompting user for files.
+ *
+ * Built for Swing.
+ *
+ * @author ben
+ *
+ */
+public class SimpleFileChooser {
+ private static File doOpenFile(final Component parent, final String title, final JFileChooser files) {
+ if (title == null) throw new NullPointerException("Title must not be null");
+
+ files.setDialogTitle(title);
+
+ boolean success = false;
+
+ while (!success) {
+ try {
+ maybeDoOpenFile(parent, files);
+
+ success = true;
+ } catch (final FileNotChosenException fncx) {
+ // We don't care about specifics
+ SimpleDialogs.showError(parent, "I/O Error", "Please pick a file to open");
+ }
+ }
+
+ return files.getSelectedFile();
+ }
+
+ private static File doSaveFile(final Component parent, final String title, final JFileChooser files) {
+ if (title == null) throw new NullPointerException("Title must not be null");
+
+ files.setDialogTitle(title);
+
+ final boolean success = false;
+
+ while (!success) {
+ try {
+ maybeDoSaveFile(parent, files);
+
+ return files.getSelectedFile();
+ } catch (final FileNotChosenException fncex) {
+ // We don't care about specifics
+ SimpleDialogs.showError(parent, "I/O Error", "Please pick a file to save to");
+ }
+ }
+ }
+
+ /**
+ * Prompt the user with a "Open File..." dialog. Keeps prompting them
+ * until they pick a file.
+ *
+ * @param parent
+ * The component to use as the parent for the dialog.
+ * @param title
+ * The title of the dialog to prompt with.
+ * @return The file the user has chosen.
+ */
+ public static File getOpenFile(final Component parent, final String title) {
+ final JFileChooser files = new JFileChooser();
+
+ return doOpenFile(parent, title, files);
+ }
+
+ /**
+ * Prompt the user with a "Open File..." dialog. Keeps prompting them
+ * until they pick a file.
+ *
+ * @param parent
+ * The component to use as the parent for the dialog.
+ * @param title
+ * The title of the dialog to prompt with.
+ * @param extensions
+ * The list of file extensions the file should have.
+ * @return The file the user has chosen.
+ */
+ public static File getOpenFile(final Component parent, final String title, final String... extensions) {
+ final JFileChooser files = new JFileChooser();
+
+ files.addChoosableFileFilter(new ExtensionFileFilter(extensions));
+
+ return doOpenFile(parent, title, files);
+ }
+
+ /**
+ * Prompt the user with a "Save File..." dialog.
+ *
+ * @param parent
+ * The component to use as the parent for the dialog.
+ * @param title
+ * The title of the dialog to prompt with.
+ * @return The file the user chose.
+ */
+ public static File getSaveFile(final Component parent, final String title) {
+ final JFileChooser files = new JFileChooser();
+
+ return doSaveFile(parent, title, files);
+ }
+
+ /**
+ * Prompt the user with a "Save File..." dialog.
+ *
+ * @param parent
+ * The component to use as the parent for the dialog.
+ * @param title
+ * The title of the dialog to prompt with.
+ * @param extensions
+ * The extensions of the files the user can choose.
+ * @return The file the user chose.
+ */
+ public static File getSaveFile(final Component parent, final String title, final String... extensions) {
+ final JFileChooser files = new JFileChooser();
+
+ files.addChoosableFileFilter(new ExtensionFileFilter(extensions));
+
+ return doSaveFile(parent, title, files);
+ }
+
+ private static void maybeDoOpenFile(final Component parent, final JFileChooser files)
+ throws FileNotChosenException {
+ if (parent == null)
+ throw new NullPointerException("Parent must not be null");
+ else if (files == null) throw new NullPointerException("File chooser must not be null");
+
+ final int result = files.showSaveDialog(parent);
+
+ if (result != JFileChooser.APPROVE_OPTION) throw new FileNotChosenException();
+ }
+
+ private static void maybeDoSaveFile(final Component parent, final JFileChooser files)
+ throws FileNotChosenException {
+ if (parent == null)
+ throw new NullPointerException("Parent must not be null");
+ else if (files == null) throw new NullPointerException("File chooser must not be null");
+
+ final int result = files.showSaveDialog(parent);
+
+ if (result != JFileChooser.APPROVE_OPTION) throw new FileNotChosenException();
+ }
+
+ /**
+ * Prompt the user with a "Open File..." dialog.
+ *
+ * @param parent
+ * The component to use as the parent for the dialog.
+ * @param title
+ * The title of the dialog to prompt with.
+ * @return The file if the user chose one or null if they didn't.
+ */
+ public static File maybeOpenFile(final Component parent, final String title) {
+ if (title == null) throw new NullPointerException("Title must not be null");
+
+ final JFileChooser files = new JFileChooser();
+ files.setDialogTitle(title);
+
+ try {
+ maybeDoOpenFile(parent, files);
+ } catch (final FileNotChosenException fncex) {
+ // We don't care about specifics
+ }
+
+ return files.getSelectedFile();
+ }
+
+ /**
+ * Prompt the user with a "Save File..." dialog.
+ *
+ * @param parent
+ * The component to use as the parent for the dialog.
+ * @param title
+ * The title of the dialog to prompt with.
+ * @return The file if the user chose one or null if they didn't.
+ */
+ public static File maybeSaveFile(final Component parent, final String title) {
+ if (title == null) throw new NullPointerException("Title must not be null");
+
+ final JFileChooser files = new JFileChooser();
+ files.setDialogTitle(title);
+
+ try {
+ maybeDoSaveFile(parent, files);
+ } catch (final FileNotChosenException fncex) {
+ // We don't care about specifics
+ }
+
+ return files.getSelectedFile();
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/SimpleInternalDialogs.java b/base/src/main/java/bjc/utils/gui/SimpleInternalDialogs.java
new file mode 100644
index 0000000..5237557
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/SimpleInternalDialogs.java
@@ -0,0 +1,208 @@
+package bjc.utils.gui;
+
+import java.awt.Component;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.swing.JOptionPane;
+
+/**
+ * Utility class for getting simple input from the user.
+ *
+ * Modified to work with JDesktopPanes
+ *
+ * @author ben
+ *
+ */
+public class SimpleInternalDialogs {
+ /**
+ * Get a bounded integer from the user.
+ *
+ * @param parent
+ * The parent component for the dialogs.
+ * @param title
+ * The title for the dialogs.
+ * @param prompt
+ * The prompt to tell the user what to enter.
+ * @param lowerBound
+ * The lower integer bound to accept.
+ * @param upperBound
+ * The upper integer bound to accept.
+ * @return A int within the specified bounds.
+ */
+ public static int getBoundedInt(final Component parent, final String title, final String prompt,
+ final int lowerBound, final int upperBound) {
+ return getValue(parent, title, prompt, (strang) -> {
+ try {
+ final int value = Integer.parseInt(strang);
+
+ return value < upperBound && value > lowerBound;
+ } catch (final NumberFormatException nfex) {
+ // We don't care about the specifics of the
+ // exception, just
+ // that this value isn't good
+ return false;
+ }
+ }, Integer::parseInt);
+ }
+
+ /**
+ * Get a integer from the user
+ *
+ * @param parent
+ * The parent component for dialogs.
+ * @param title
+ * The title for dialogs.
+ * @param prompt
+ * The prompt to tell the user what to enter.
+ * @return A int.
+ */
+ public static int getInt(final Component parent, final String title, final String prompt) {
+ return getValue(parent, title, prompt, strang -> {
+ try {
+ Integer.parseInt(strang);
+ return true;
+ } catch (final NumberFormatException nfex) {
+ // We don't care about this exception, just mark
+ // the value
+ // as not good
+ return false;
+ }
+ }, Integer::parseInt);
+ }
+
+ /**
+ * Get a string from the user
+ *
+ * @param parent
+ * The parent component for dialogs.
+ * @param title
+ * The title for the dialogs.
+ * @param prompt
+ * The prompt to tell the user what to enter.
+ * @return A string.
+ */
+ public static String getString(final Component parent, final String title, final String prompt) {
+ if (parent == null)
+ throw new NullPointerException("Parent must not be null");
+ else if (title == null)
+ throw new NullPointerException("Title must not be null");
+ else if (prompt == null) throw new NullPointerException("Prompt must not be null");
+
+ return JOptionPane.showInternalInputDialog(parent, prompt, title, JOptionPane.QUESTION_MESSAGE);
+ }
+
+ /**
+ * Get a value parsable from a string from the user.
+ *
+ * @param <E>
+ * The type of the value parsed from the string
+ *
+ * @param parent
+ * The parent component for dialogs.
+ * @param title
+ * The title for dialogs.
+ * @param prompt
+ * The prompt to tell the user what to enter.
+ * @param validator
+ * A predicate to determine if a input is valid.
+ * @param transformer
+ * The function to transform the string into a value.
+ * @return The value parsed from a string.
+ */
+ public static <E> E getValue(final Component parent, final String title, final String prompt,
+ final Predicate<String> validator, final Function<String, E> transformer) {
+ if (validator == null)
+ throw new NullPointerException("Validator must not be null");
+ else if (transformer == null) throw new NullPointerException("Transformer must not be null");
+
+ String strang = getString(parent, title, prompt);
+
+ while (!validator.test(strang)) {
+ showError(parent, "I/O Error", "Please enter a valid value");
+
+ strang = getString(parent, title, prompt);
+ }
+
+ return transformer.apply(strang);
+ }
+
+ /**
+ * Get a whole number from the user.
+ *
+ * @param parent
+ * The parent component for dialogs.
+ * @param title
+ * The title for dialogs.
+ * @param prompt
+ * The prompt to tell the user what to enter.
+ * @return A whole number.
+ */
+ public static int getWhole(final Component parent, final String title, final String prompt) {
+ return getBoundedInt(parent, title, prompt, 0, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Ask the user a Yes/No question.
+ *
+ * @param parent
+ * The parent component for dialogs.
+ * @param title
+ * The title for dialogs.
+ * @param question
+ * The question to ask the user.
+ * @return True if the user said yes, false otherwise.
+ */
+ public static boolean getYesNo(final Component parent, final String title, final String question) {
+ if (parent == null)
+ throw new NullPointerException("Parent must not be null");
+ else if (title == null)
+ throw new NullPointerException("Title must not be null");
+ else if (question == null) throw new NullPointerException("Question must not be null");
+
+ final int result = JOptionPane.showInternalConfirmDialog(parent, question, title,
+ JOptionPane.YES_NO_OPTION);
+
+ return result == JOptionPane.YES_OPTION ? true : false;
+ }
+
+ /**
+ * Show a error message to the user
+ *
+ * @param parent
+ * The parent component for dialogs.
+ * @param title
+ * The title for dialogs.
+ * @param message
+ * The error to show the user.
+ */
+ public static void showError(final Component parent, final String title, final String message) {
+ if (parent == null)
+ throw new NullPointerException("Parent must not be null");
+ else if (title == null)
+ throw new NullPointerException("Title must not be null");
+ else if (message == null) throw new NullPointerException("Error message must not be null");
+
+ JOptionPane.showInternalMessageDialog(parent, message, title, JOptionPane.ERROR_MESSAGE);
+ }
+
+ /**
+ * Show an informative message to the user
+ *
+ * @param parent
+ * The parent for this dialog
+ * @param title
+ * Show the title for this dialog
+ * @param message
+ * Show the message for this dialog
+ */
+ public static void showMessage(final Component parent, final String title, final String message) {
+ if (parent == null)
+ throw new NullPointerException("Parent must not be null");
+ else if (title == null)
+ throw new NullPointerException("Title must not be null");
+ else if (message == null) throw new NullPointerException("Message must not be null");
+
+ JOptionPane.showInternalMessageDialog(parent, title, message, JOptionPane.INFORMATION_MESSAGE);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/SimpleInternalFrame.java b/base/src/main/java/bjc/utils/gui/SimpleInternalFrame.java
new file mode 100644
index 0000000..afb498e
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/SimpleInternalFrame.java
@@ -0,0 +1,40 @@
+package bjc.utils.gui;
+
+import javax.swing.JInternalFrame;
+
+/**
+ * A simple internal frame class
+ *
+ * @author ben
+ *
+ */
+public class SimpleInternalFrame extends JInternalFrame {
+ private static final long serialVersionUID = -2966801321260716617L;
+
+ /**
+ * Create a new blank internal frame
+ */
+ public SimpleInternalFrame() {
+ super();
+ }
+
+ /**
+ * Create a new blank internal frame with a specific title
+ *
+ * @param title
+ * The title of the internal frame
+ */
+ public SimpleInternalFrame(final String title) {
+ super(title);
+ }
+
+ protected void setupFrame() {
+ setSize(320, 240);
+
+ setResizable(true);
+
+ setClosable(true);
+ setMaximizable(true);
+ setIconifiable(true);
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/bjc/utils/gui/SimpleJList.java b/base/src/main/java/bjc/utils/gui/SimpleJList.java
new file mode 100644
index 0000000..411d0db
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/SimpleJList.java
@@ -0,0 +1,49 @@
+package bjc.utils.gui;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JList;
+import javax.swing.ListModel;
+
+/**
+ * Utility class for making JLists and their models.
+ *
+ * @author ben
+ *
+ */
+public class SimpleJList {
+ /**
+ * Create a new JList from a given list.
+ *
+ * @param <E>
+ * The type of data in the JList
+ *
+ * @param source
+ * The list to populate the JList with.
+ * @return A JList populated with the elements from ls.
+ */
+ public static <E> JList<E> buildFromList(final Iterable<E> source) {
+ if (source == null) throw new NullPointerException("Source must not be null");
+
+ return new JList<>(buildModel(source));
+ }
+
+ /**
+ * Create a new list model from a given list.
+ *
+ * @param <E>
+ * The type of data in the list model
+ *
+ * @param source
+ * The list to fill the list model from.
+ * @return A list model populated with the elements from ls.
+ */
+ public static <E> ListModel<E> buildModel(final Iterable<E> source) {
+ if (source == null) throw new NullPointerException("Source must not be null");
+
+ final DefaultListModel<E> defaultModel = new DefaultListModel<>();
+
+ source.forEach(defaultModel::addElement);
+
+ return defaultModel;
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/SimpleTitledBorder.java b/base/src/main/java/bjc/utils/gui/SimpleTitledBorder.java
new file mode 100644
index 0000000..9b01507
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/SimpleTitledBorder.java
@@ -0,0 +1,25 @@
+package bjc.utils.gui;
+
+import javax.swing.border.EtchedBorder;
+import javax.swing.border.TitledBorder;
+
+/**
+ * A simple border with a title attached to it.
+ *
+ * @author ben
+ *
+ */
+public class SimpleTitledBorder extends TitledBorder {
+ // Version ID for serialization
+ private static final long serialVersionUID = -5655969079949148487L;
+
+ /**
+ * Create a new border with the specified title.
+ *
+ * @param title
+ * The title for the border.
+ */
+ public SimpleTitledBorder(final String title) {
+ super(new EtchedBorder(), title);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/TextAreaOutputStream.java b/base/src/main/java/bjc/utils/gui/TextAreaOutputStream.java
new file mode 100644
index 0000000..fbc58ed
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/TextAreaOutputStream.java
@@ -0,0 +1,35 @@
+package bjc.utils.gui;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.swing.JTextArea;
+
+/**
+ * An output stream that prints to a JTextArea
+ *
+ * @author epr
+ * @author Levente S\u00e1ntha (lsantha@users.sourceforge.net)
+ */
+public class TextAreaOutputStream extends OutputStream {
+ private final JTextArea textArea;
+
+ /**
+ * Create a new output stream attached to a textarea
+ *
+ * @param console
+ * The textarea to write to
+ */
+ public TextAreaOutputStream(final JTextArea console) {
+ this.textArea = console;
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ textArea.append("" + (char) b);
+
+ if (b == '\n') {
+ textArea.repaint();
+ }
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/awt/ExtensionFileFilter.java b/base/src/main/java/bjc/utils/gui/awt/ExtensionFileFilter.java
new file mode 100644
index 0000000..eb60ae2
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/awt/ExtensionFileFilter.java
@@ -0,0 +1,50 @@
+package bjc.utils.gui.awt;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.List;
+
+import bjc.utils.funcdata.FunctionalList;
+import bjc.utils.funcdata.IList;
+
+/**
+ * Filter a set of filenames by extension.
+ *
+ * Built for AWT
+ *
+ * @author ben
+ *
+ */
+public class ExtensionFileFilter implements FilenameFilter {
+ /**
+ * The list of extensions to filter
+ */
+ private final IList<String> extensions;
+
+ /**
+ * Create a new filter only showing files with the specified extensions.
+ *
+ * @param exts
+ * The extensions to show in this filter.
+ */
+ public ExtensionFileFilter(final List<String> exts) {
+ if (exts == null) throw new NullPointerException("Extensions must not be null");
+
+ extensions = new FunctionalList<>(exts);
+ }
+
+ /**
+ * Create a new filter only showing files with the specified extensions.
+ *
+ * @param exts
+ * The extensions to show in this filter.
+ */
+ public ExtensionFileFilter(final String... exts) {
+ extensions = new FunctionalList<>(exts);
+ }
+
+ @Override
+ public boolean accept(final File directory, final String name) {
+ return extensions.anyMatch(name::endsWith);
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/bjc/utils/gui/awt/SimpleFileDialog.java b/base/src/main/java/bjc/utils/gui/awt/SimpleFileDialog.java
new file mode 100644
index 0000000..77a4a59
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/awt/SimpleFileDialog.java
@@ -0,0 +1,144 @@
+package bjc.utils.gui.awt;
+
+import java.awt.FileDialog;
+import java.awt.Frame;
+import java.io.File;
+import java.io.FilenameFilter;
+
+import bjc.utils.gui.SimpleDialogs;
+
+/**
+ * A simple way to get the user to pick a file
+ *
+ * Built for AWT.
+ *
+ * @author ben
+ *
+ */
+public class SimpleFileDialog {
+ /**
+ * Prompt the user to pick a file to open
+ *
+ * @param parent
+ * The parent of the file picker
+ * @param title
+ * The title of the file picker
+ * @return The file the user picked
+ */
+ public static File getOpenFile(final Frame parent, final String title) {
+ return getOpenFile(parent, title, (String[]) null);
+ }
+
+ /**
+ * Prompt the user to pick a file to open
+ *
+ * @param parent
+ * The parent of the file picker
+ * @param title
+ * The title of the file picker
+ * @param extensions
+ * The extensions to accept as valid
+ * @return The file the user picked
+ */
+ public static File getOpenFile(final Frame parent, final String title, final String... extensions) {
+ if (parent == null)
+ throw new NullPointerException("Parent must not be null");
+ else if (title == null) throw new NullPointerException("Title must not be null");
+
+ final FileDialog chooser = new FileDialog(parent, title, FileDialog.LOAD);
+
+ if (extensions != null) {
+ final FilenameFilter filter = new ExtensionFileFilter(extensions);
+ chooser.setFilenameFilter(filter);
+ }
+
+ chooser.setVisible(true);
+
+ while (chooser.getFile() == null) {
+ SimpleDialogs.showError(parent, "File I/O Error", "Please choose a file to open.");
+ chooser.setVisible(true);
+ }
+
+ return chooser.getFiles()[0];
+ }
+
+ /**
+ * Prompt the user to pick a file to open
+ *
+ * @param parent
+ * The parent of the file picker
+ * @param title
+ * The title of the file picker
+ * @param extensions
+ * The extensions to accept as valid
+ * @return The file the user picked
+ */
+ public static File[] getOpenFiles(final Frame parent, final String title, final String... extensions) {
+ if (parent == null)
+ throw new NullPointerException("Parent must not be null");
+ else if (title == null) throw new NullPointerException("Title must not be null");
+
+ final FileDialog chooser = new FileDialog(parent, title, FileDialog.LOAD);
+
+ if (extensions != null) {
+ final FilenameFilter filter = new ExtensionFileFilter(extensions);
+ chooser.setFilenameFilter(filter);
+ }
+
+ chooser.setMultipleMode(true);
+ chooser.setVisible(true);
+
+ while (chooser.getFile() == null) {
+ SimpleDialogs.showError(parent, "File I/O Error", "Please choose a file to open.");
+ chooser.setVisible(true);
+ }
+
+ return chooser.getFiles();
+ }
+
+ /**
+ * Prompt the user to pick a file to save
+ *
+ * @param parent
+ * The parent of the file picker
+ * @param title
+ * The title of the file picker
+ * @return The file the user picked
+ */
+ public static File getSaveFile(final Frame parent, final String title) {
+ return getSaveFile(parent, title, (String[]) null);
+ }
+
+ /**
+ * Prompt the user to pick a file to save
+ *
+ * @param parent
+ * The parent of the file picker
+ * @param title
+ * The title of the file picker
+ * @param extensions
+ * The extensions to accept as valid
+ * @return The file the user picked
+ */
+ public static File getSaveFile(final Frame parent, final String title, final String... extensions) {
+ if (parent == null)
+ throw new NullPointerException("Parent must not be null");
+ else if (title == null) throw new NullPointerException("Title must not be null");
+
+ final FileDialog chooser = new FileDialog(parent, title, FileDialog.SAVE);
+
+ if (extensions != null) {
+ final FilenameFilter filter = new ExtensionFileFilter(extensions);
+ chooser.setFilenameFilter(filter);
+ }
+
+ chooser.setVisible(true);
+
+ while (chooser.getFile() == null) {
+ SimpleDialogs.showError(parent, "File I/O Error", "Please choose a file to save to.");
+ chooser.setVisible(true);
+ }
+
+ return chooser.getFiles()[0];
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/layout/AutosizeLayout.java b/base/src/main/java/bjc/utils/gui/layout/AutosizeLayout.java
new file mode 100644
index 0000000..6f384f2
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/layout/AutosizeLayout.java
@@ -0,0 +1,22 @@
+package bjc.utils.gui.layout;
+
+import java.awt.GridLayout;
+
+/**
+ * A layout that simply holds one component that it auto-resizes whenever it is
+ * resized.
+ *
+ * @author ben
+ *
+ */
+public class AutosizeLayout extends GridLayout {
+ // Version id for serialization
+ private static final long serialVersionUID = -2495693595953396924L;
+
+ /**
+ * Create a new auto-size layout.
+ */
+ public AutosizeLayout() {
+ super(1, 1);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/layout/HLayout.java b/base/src/main/java/bjc/utils/gui/layout/HLayout.java
new file mode 100644
index 0000000..4ed1661
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/layout/HLayout.java
@@ -0,0 +1,25 @@
+package bjc.utils.gui.layout;
+
+import java.awt.GridLayout;
+
+/**
+ * A layout manager that lays out its components horizontally, evenly sizing
+ * them.
+ *
+ * @author ben
+ *
+ */
+public class HLayout extends GridLayout {
+ // Version ID for serialization
+ private static final long serialVersionUID = 1244964456966270026L;
+
+ /**
+ * Create a new horizontal layout with the specified number of columns.
+ *
+ * @param columns
+ * The number of columns in this layout.
+ */
+ public HLayout(final int columns) {
+ super(1, columns);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/layout/VLayout.java b/base/src/main/java/bjc/utils/gui/layout/VLayout.java
new file mode 100644
index 0000000..6993365
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/layout/VLayout.java
@@ -0,0 +1,25 @@
+package bjc.utils.gui.layout;
+
+import java.awt.GridLayout;
+
+/**
+ * A layout that lays out its components vertically, evenly sharing space among
+ * them.
+ *
+ * @author ben
+ *
+ */
+public class VLayout extends GridLayout {
+ // Version ID for serializations
+ private static final long serialVersionUID = -6417962941602322663L;
+
+ /**
+ * Create a new vertical layout with the specified number of rows.
+ *
+ * @param rows
+ * The number of rows.
+ */
+ public VLayout(final int rows) {
+ super(rows, 1);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/panels/DropdownListPanel.java b/base/src/main/java/bjc/utils/gui/panels/DropdownListPanel.java
new file mode 100644
index 0000000..4f71d38
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/panels/DropdownListPanel.java
@@ -0,0 +1,73 @@
+package bjc.utils.gui.panels;
+
+import java.awt.BorderLayout;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.ListSelectionModel;
+
+import bjc.utils.funcdata.IList;
+import bjc.utils.gui.layout.AutosizeLayout;
+import bjc.utils.gui.layout.HLayout;
+
+/**
+ * A panel that allows you to select choices from a dropdown list
+ *
+ * @author ben
+ *
+ */
+public class DropdownListPanel extends JPanel {
+ private static final long serialVersionUID = 2719963952350133541L;
+
+ /**
+ * Create a new dropdown list panel
+ *
+ * @param <T>
+ * The type of items in the dropdown list
+ * @param type
+ * The label of the type of items in the list
+ * @param model
+ * The model to put items into
+ * @param choices
+ * The items to choose from
+ */
+ public <T> DropdownListPanel(final String type, final DefaultListModel<T> model, final IList<T> choices) {
+ setLayout(new AutosizeLayout());
+
+ final JPanel itemInputPanel = new JPanel();
+ itemInputPanel.setLayout(new BorderLayout());
+
+ final JPanel addItemPanel = new JPanel();
+ addItemPanel.setLayout(new HLayout(2));
+
+ final JComboBox<T> addItemBox = new JComboBox<>();
+ choices.forEach(addItemBox::addItem);
+
+ final JButton addItemButton = new JButton("Add " + type);
+
+ addItemPanel.add(addItemBox);
+ addItemPanel.add(addItemButton);
+
+ final JList<T> itemList = new JList<>(model);
+ itemList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+ final JButton removeItemButton = new JButton("Remove " + type);
+
+ addItemButton.addActionListener((ev) -> {
+ model.addElement(addItemBox.getItemAt(addItemBox.getSelectedIndex()));
+ });
+
+ removeItemButton.addActionListener((ev) -> {
+ model.remove(itemList.getSelectedIndex());
+ });
+
+ itemInputPanel.add(addItemPanel, BorderLayout.PAGE_START);
+ itemInputPanel.add(itemList, BorderLayout.CENTER);
+ itemInputPanel.add(removeItemButton, BorderLayout.PAGE_END);
+
+ add(itemInputPanel);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/panels/FormattedInputPanel.java b/base/src/main/java/bjc/utils/gui/panels/FormattedInputPanel.java
new file mode 100644
index 0000000..2cecf0c
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/panels/FormattedInputPanel.java
@@ -0,0 +1,66 @@
+package bjc.utils.gui.panels;
+
+import java.util.function.Consumer;
+
+import javax.swing.JFormattedTextField;
+import javax.swing.JFormattedTextField.AbstractFormatter;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import bjc.utils.gui.layout.HLayout;
+
+/**
+ * A simple panel allowing for input of a single formatted value
+ *
+ * @author ben
+ *
+ * @param <InputVal>
+ * The type of value being formatted
+ */
+public class FormattedInputPanel<InputVal> extends JPanel {
+ private static final long serialVersionUID = 5232016563558588031L;
+
+ private final JFormattedTextField field;
+
+ /**
+ * Create a new formatted input panel
+ *
+ * @param label
+ * The label for this panel
+ * @param length
+ * The length of this panel
+ * @param formatter
+ * The formatter to use for input
+ * @param reciever
+ * The action to call whenever the value changes
+ */
+ @SuppressWarnings("unchecked")
+ public FormattedInputPanel(final String label, final int length, final AbstractFormatter formatter,
+ final Consumer<InputVal> reciever) {
+ setLayout(new HLayout(2));
+
+ final JLabel lab = new JLabel(label);
+ field = new JFormattedTextField(formatter);
+
+ field.setColumns(length);
+ field.setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT);
+ field.addPropertyChangeListener("value", (event) -> {
+ // This is safe, because InputVal should be the type of
+ // whatever object the formatter is returning
+ reciever.accept((InputVal) field.getValue());
+ });
+
+ add(lab);
+ add(field);
+ }
+
+ /**
+ * Reset the value in this panel to a specified value
+ *
+ * @param value
+ * The value to set the panel to
+ */
+ public void resetValues(final InputVal value) {
+ field.setValue(value);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/panels/HolderOutputPanel.java b/base/src/main/java/bjc/utils/gui/panels/HolderOutputPanel.java
new file mode 100644
index 0000000..653dace
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/panels/HolderOutputPanel.java
@@ -0,0 +1,79 @@
+package bjc.utils.gui.panels;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.Timer;
+
+import bjc.utils.data.IHolder;
+import bjc.utils.gui.layout.HLayout;
+
+/**
+ * A panel that outputs a value bound to a {@link IHolder}
+ *
+ * @author ben
+ *
+ */
+public class HolderOutputPanel extends JPanel {
+ private static final long serialVersionUID = 166573313903782080L;
+
+ private Timer updater;
+ private final JLabel value;
+ private final int nDelay;
+ private final IHolder<String> val;
+
+ /**
+ * Create a new display panel, backed by a holder
+ *
+ * @param lab
+ * The label to attach to this field
+ * @param valueHolder
+ * The holder to get the value from
+ * @param nDelay
+ * The delay in ms between value updates
+ */
+ public HolderOutputPanel(final String lab, final IHolder<String> valueHolder, final int nDelay) {
+ this.val = valueHolder;
+ this.nDelay = nDelay;
+
+ setLayout(new HLayout(2));
+
+ final JLabel label = new JLabel(lab);
+ value = new JLabel("(stopped)");
+
+ updater = new Timer(nDelay, (event) -> {
+ value.setText(valueHolder.getValue());
+ });
+
+ add(label);
+ add(value);
+ }
+
+ /**
+ * Set this panel back to its initial state
+ */
+ public void reset() {
+ stopUpdating();
+
+ value.setText("(stopped)");
+
+ updater = new Timer(nDelay, (event) -> {
+ value.setText(val.getValue());
+ });
+ }
+
+ /**
+ * Start updating the contents of the field from the holder
+ */
+ public void startUpdating() {
+ updater.start();
+ }
+
+ /**
+ * Stop updating the contents of the field from the holder
+ */
+ public void stopUpdating() {
+ updater.stop();
+
+ value.setText(value.getText() + " (stopped)");
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/panels/ListParameterPanel.java b/base/src/main/java/bjc/utils/gui/panels/ListParameterPanel.java
new file mode 100644
index 0000000..cca73d5
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/panels/ListParameterPanel.java
@@ -0,0 +1,133 @@
+package bjc.utils.gui.panels;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.ListSelectionModel;
+
+import bjc.utils.funcdata.IList;
+import bjc.utils.gui.SimpleJList;
+import bjc.utils.gui.layout.HLayout;
+import bjc.utils.gui.layout.VLayout;
+
+/**
+ * A panel that has a list of objects and ways of manipulating that list
+ *
+ * @author ben
+ *
+ * @param <E>
+ * The type of data stored in the list
+ */
+public class ListParameterPanel<E> extends JPanel {
+ // Version id for serialization
+ private static final long serialVersionUID = 3442971104975491571L;
+
+ /**
+ * Create a new panel using the specified actions for doing things
+ *
+ * @param add
+ * The action that provides items
+ * @param edit
+ * The action that edits items
+ * @param remove
+ * The action that removes items
+ */
+ public ListParameterPanel(final Supplier<E> add, final Consumer<E> edit, final Consumer<E> remove) {
+ this(add, edit, remove, null);
+ }
+
+ /**
+ * Create a new panel using the specified actions for doing things
+ *
+ * @param add
+ * The action that provides items
+ * @param edit
+ * The action that edits items
+ * @param remove
+ * The action that removes items
+ * @param defaults
+ * The default values to put in the list
+ */
+ public ListParameterPanel(final Supplier<E> add, final Consumer<E> edit, final Consumer<E> remove,
+ final IList<E> defaults) {
+ setLayout(new VLayout(2));
+
+ JList<E> list;
+
+ if (defaults != null) {
+ list = SimpleJList.buildFromList(defaults.toIterable());
+ } else {
+ list = new JList<>(new DefaultListModel<>());
+ }
+
+ list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+ final JPanel buttonPanel = new JPanel();
+
+ int numButtons = 0;
+
+ if (add != null) {
+ numButtons++;
+ }
+
+ if (edit != null) {
+ numButtons++;
+ }
+
+ if (remove != null) {
+ numButtons++;
+ }
+
+ buttonPanel.setLayout(new HLayout(numButtons));
+
+ JButton addParam = null;
+
+ if (add != null) {
+ addParam = new JButton("Add...");
+ addParam.addActionListener((event) -> {
+ final DefaultListModel<E> model = (DefaultListModel<E>) list.getModel();
+
+ model.addElement(add.get());
+ });
+ }
+
+ JButton editParam = null;
+
+ if (edit != null) {
+ editParam = new JButton("Edit...");
+ editParam.addActionListener((event) -> {
+ edit.accept(list.getSelectedValue());
+ });
+ }
+
+ JButton removeParam = null;
+
+ if (remove != null) {
+ removeParam = new JButton("Remove...");
+ removeParam.addActionListener((event) -> {
+ final DefaultListModel<E> model = (DefaultListModel<E>) list.getModel();
+
+ remove.accept(model.remove(list.getSelectedIndex()));
+ });
+ }
+
+ if (add != null) {
+ buttonPanel.add(addParam);
+ }
+
+ if (edit != null) {
+ buttonPanel.add(editParam);
+ }
+
+ if (remove != null) {
+ buttonPanel.add(removeParam);
+ }
+
+ add(list);
+ add(buttonPanel);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/panels/SimpleInputPanel.java b/base/src/main/java/bjc/utils/gui/panels/SimpleInputPanel.java
new file mode 100644
index 0000000..65c533d
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/panels/SimpleInputPanel.java
@@ -0,0 +1,45 @@
+package bjc.utils.gui.panels;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+/**
+ * A simple component for text input
+ *
+ * @author ben
+ *
+ */
+public class SimpleInputPanel extends JPanel {
+ private static final long serialVersionUID = -4734279623645236868L;
+
+ /**
+ * The text field containing the input value
+ */
+ public final JTextField inputValue;
+
+ /**
+ * Create a new input panel
+ *
+ * @param label
+ * The label for the field
+ * @param columns
+ * The number of columns of text input to take
+ */
+ public SimpleInputPanel(final String label, final int columns) {
+ setLayout(new BorderLayout());
+
+ final JLabel inputLabel = new JLabel(label);
+
+ if (columns < 1) {
+ inputValue = new JTextField();
+ } else {
+ inputValue = new JTextField(columns);
+ }
+
+ add(inputLabel, BorderLayout.LINE_START);
+ add(inputValue, BorderLayout.CENTER);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/panels/SimpleListPanel.java b/base/src/main/java/bjc/utils/gui/panels/SimpleListPanel.java
new file mode 100644
index 0000000..edc1797
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/panels/SimpleListPanel.java
@@ -0,0 +1,93 @@
+package bjc.utils.gui.panels;
+
+import java.awt.BorderLayout;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+
+import bjc.utils.gui.layout.AutosizeLayout;
+import bjc.utils.gui.layout.HLayout;
+
+/**
+ * A simple list of strings
+ *
+ * @author ben
+ *
+ */
+public class SimpleListPanel extends JPanel {
+ private static final long serialVersionUID = 2719963952350133541L;
+
+ private static void addItem(final DefaultListModel<String> model, final Predicate<String> verifier,
+ final Consumer<String> onFailure, final JTextField addItemField) {
+ final String potentialItem = addItemField.getText();
+
+ if (verifier == null || verifier.test(potentialItem)) {
+ model.addElement(potentialItem);
+ } else {
+ onFailure.accept(potentialItem);
+ }
+
+ addItemField.setText("");
+ }
+
+ /**
+ * Create a new list panel
+ *
+ * @param type
+ * The type of things in the list
+ * @param model
+ * The model to put items into
+ * @param verifier
+ * The predicate to use to verify items
+ * @param onFailure
+ * The function to call when an item doesn't verify
+ */
+ public SimpleListPanel(final String type, final DefaultListModel<String> model,
+ final Predicate<String> verifier, final Consumer<String> onFailure) {
+ setLayout(new AutosizeLayout());
+
+ final JPanel itemInputPanel = new JPanel();
+ itemInputPanel.setLayout(new BorderLayout());
+
+ final JPanel addItemPanel = new JPanel();
+ addItemPanel.setLayout(new HLayout(2));
+
+ final JTextField addItemField = new JTextField(255);
+ final JButton addItemButton = new JButton("Add " + type);
+
+ addItemPanel.add(addItemField);
+ addItemPanel.add(addItemButton);
+
+ final JList<String> itemList = new JList<>(model);
+ itemList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+ final JScrollPane listScroller = new JScrollPane(itemList);
+
+ final JButton removeItemButton = new JButton("Remove " + type);
+
+ addItemButton.addActionListener((ev) -> {
+ addItem(model, verifier, onFailure, addItemField);
+ });
+
+ addItemField.addActionListener((ev) -> {
+ addItem(model, verifier, onFailure, addItemField);
+ });
+
+ removeItemButton.addActionListener((ev) -> {
+ model.remove(itemList.getSelectedIndex());
+ });
+
+ itemInputPanel.add(addItemPanel, BorderLayout.PAGE_START);
+ itemInputPanel.add(listScroller, BorderLayout.CENTER);
+ itemInputPanel.add(removeItemButton, BorderLayout.PAGE_END);
+
+ add(itemInputPanel);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/panels/SimpleSpinnerPanel.java b/base/src/main/java/bjc/utils/gui/panels/SimpleSpinnerPanel.java
new file mode 100644
index 0000000..6106182
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/panels/SimpleSpinnerPanel.java
@@ -0,0 +1,42 @@
+package bjc.utils.gui.panels;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.SpinnerModel;
+
+/**
+ * A simple spinner control
+ *
+ * @author ben
+ *
+ */
+public class SimpleSpinnerPanel extends JPanel {
+ private static final long serialVersionUID = -4734279623645236868L;
+
+ /**
+ * The spinner being used
+ */
+ public final JSpinner inputValue;
+
+ /**
+ * Create a new spinner panel
+ *
+ * @param label
+ * The label for the spinner
+ * @param model
+ * The model to attach to the spinner
+ */
+ public SimpleSpinnerPanel(final String label, final SpinnerModel model) {
+ setLayout(new BorderLayout());
+
+ final JLabel inputLabel = new JLabel(label);
+
+ inputValue = new JSpinner(model);
+
+ add(inputLabel, BorderLayout.LINE_START);
+ add(inputValue, BorderLayout.CENTER);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/panels/SliderInputPanel.java b/base/src/main/java/bjc/utils/gui/panels/SliderInputPanel.java
new file mode 100644
index 0000000..e6a6da4
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/panels/SliderInputPanel.java
@@ -0,0 +1,187 @@
+package bjc.utils.gui.panels;
+
+import java.text.ParseException;
+import java.util.function.Consumer;
+
+import javax.swing.JFormattedTextField;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+
+import bjc.utils.gui.layout.HLayout;
+
+/**
+ * A simple input panel for a slider-controlled value and a manual-input field
+ * for setting the slider
+ *
+ * @author ben
+ *
+ */
+public class SliderInputPanel extends JPanel {
+ private final class NumberFormatter extends JFormattedTextField.AbstractFormatter {
+ private static final long serialVersionUID = -4448291795913908270L;
+
+ private final int minValue;
+ private final int maxValue;
+
+ private final int initValue;
+
+ public NumberFormatter(final SliderSettings settings) {
+ minValue = settings.minValue;
+ maxValue = settings.maxValue;
+
+ initValue = settings.initValue;
+ }
+
+ @Override
+ public Object stringToValue(final String text) throws ParseException {
+ try {
+ final int val = Integer.parseInt(text);
+
+ if (val < minValue)
+ throw new ParseException("Value must be greater than " + minValue, 0);
+ else if (val > maxValue)
+ throw new ParseException("Value must be smaller than " + maxValue, 0);
+ else return val;
+ } catch (final NumberFormatException nfex) {
+ final ParseException pex = new ParseException("Value must be a valid integer", 0);
+
+ pex.initCause(nfex);
+
+ throw pex;
+ }
+ }
+
+ @Override
+ public String valueToString(final Object value) throws ParseException {
+ if (value == null) return Integer.toString(initValue);
+
+ return Integer.toString((Integer) value);
+ }
+ }
+
+ /**
+ * Represents the settings for a slider
+ *
+ * @author ben
+ *
+ */
+ public static class SliderSettings {
+ /**
+ * The minimum value of the slider
+ */
+ public final int minValue;
+ /**
+ * The maximum value of the slider
+ */
+ public final int maxValue;
+
+ /**
+ * The initial value of the slider
+ */
+ public final int initValue;
+
+ /**
+ * Create a new slider settings, with the initial value in the
+ * middle
+ *
+ * @param min
+ * The minimum value of the slider
+ * @param max
+ * The maximum value of the slider
+ */
+ public SliderSettings(final int min, final int max) {
+ this(min, max, (min + max) / 2);
+ }
+
+ /**
+ * Create a new set of slider sttings
+ *
+ * @param min
+ * The minimum slider value
+ * @param max
+ * The maximum slider value
+ * @param init
+ * Th initial slider value
+ */
+ public SliderSettings(final int min, final int max, final int init) {
+ minValue = min;
+ maxValue = max;
+
+ initValue = init;
+ }
+ }
+
+ private static final long serialVersionUID = 2956394160569961404L;
+ private final JSlider slider;
+ private final JFormattedTextField field;
+
+ /**
+ * Create a new slider input panel
+ *
+ * @param lab
+ * The label for the field
+ * @param settings
+ * The settings for slider values
+ * @param majorTick
+ * The setting for where to place big ticks
+ * @param minorTick
+ * The setting for where to place small ticks
+ * @param action
+ * The action to execute for a given value
+ */
+ public SliderInputPanel(final String lab, final SliderSettings settings, final int majorTick,
+ final int minorTick, final Consumer<Integer> action) {
+ setLayout(new HLayout(3));
+
+ final JLabel label = new JLabel(lab);
+
+ slider = new JSlider(settings.minValue, settings.maxValue, settings.initValue);
+ field = new JFormattedTextField(new NumberFormatter(settings));
+
+ slider.setMajorTickSpacing(majorTick);
+ slider.setMinorTickSpacing(minorTick);
+ slider.setPaintTicks(true);
+ slider.setPaintLabels(true);
+
+ slider.addChangeListener((event) -> {
+ if (slider.getValueIsAdjusting()) {
+ // Do nothing
+ } else {
+ final int val = slider.getValue();
+
+ field.setValue(val);
+
+ action.accept(val);
+ }
+ });
+
+ field.setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT);
+ field.setColumns(15);
+ field.addPropertyChangeListener("value", (event) -> {
+ final Object value = field.getValue();
+
+ if (value == null) {
+ // Do nothing
+ } else {
+ slider.setValue((Integer) value);
+ }
+ });
+
+ add(label);
+ add(slider);
+ add(field);
+ }
+
+ /**
+ * Reset the values in this panel to a specified value
+ *
+ * @param value
+ * The value to reset the fields to
+ */
+ public void resetValues(final int value) {
+ slider.setValue(value);
+
+ field.setValue(value);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/gui/panels/package-info.java b/base/src/main/java/bjc/utils/gui/panels/package-info.java
new file mode 100644
index 0000000..4361885
--- /dev/null
+++ b/base/src/main/java/bjc/utils/gui/panels/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * @author ben
+ *
+ */
+package bjc.utils.gui.panels; \ No newline at end of file