Initial commit master
authorJakob Cornell <jakob+gpg@jcornell.net>
Sun, 13 Sep 2020 00:37:57 +0000 (19:37 -0500)
committerJakob Cornell <jakob+gpg@jcornell.net>
Sun, 13 Sep 2020 00:37:57 +0000 (19:37 -0500)
15 files changed:
.gitignore [new file with mode: 0644]
build.xml [new file with mode: 0644]
src/net/jcornell/answer_check/CompareController.java [new file with mode: 0644]
src/net/jcornell/answer_check/CompareScoreEntryController.java [new file with mode: 0644]
src/net/jcornell/answer_check/CompareView.java [new file with mode: 0644]
src/net/jcornell/answer_check/KeyEditModel.java [new file with mode: 0644]
src/net/jcornell/answer_check/KeyEditView.java [new file with mode: 0644]
src/net/jcornell/answer_check/KeyListController.java [new file with mode: 0644]
src/net/jcornell/answer_check/KeyListView.java [new file with mode: 0644]
src/net/jcornell/answer_check/Main.java [new file with mode: 0644]
src/net/jcornell/answer_check/score_entry/ScoreEntryController.java [new file with mode: 0644]
src/net/jcornell/answer_check/score_entry/ScoreEntryView.java [new file with mode: 0644]
src/net/jcornell/answer_check/util/DataSave.java [new file with mode: 0644]
src/net/jcornell/answer_check/util/DocumentListenerAdapter.java [new file with mode: 0644]
src/net/jcornell/answer_check/util/GbcBuilder.java [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..e2e7327
--- /dev/null
@@ -0,0 +1 @@
+/out
diff --git a/build.xml b/build.xml
new file mode 100644 (file)
index 0000000..57a7cd6
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,27 @@
+<!-- Ant build file -->
+<project name="answer_check">
+       <property name="src" location="src" />
+       <property name="out" location="out" />
+
+       <target name="setup">
+               <mkdir dir="${out}/classes" />
+       </target>
+
+       <target name="clean">
+               <delete dir="${out}" />
+       </target>
+
+       <target name="compile" depends="setup">
+               <javac srcdir="${src}" destdir="${out}/classes" debug="true">
+                       <compilerarg value="-Xlint:unchecked" />
+               </javac>
+       </target>
+
+       <target name="jar" depends="compile">
+               <jar jarfile="${out}/build.jar" basedir="${out}/classes">
+                       <manifest>
+                               <attribute name="Main-Class" value="net.jcornell.answer_check.Main" />
+                       </manifest>
+               </jar>
+       </target>
+</project>
diff --git a/src/net/jcornell/answer_check/CompareController.java b/src/net/jcornell/answer_check/CompareController.java
new file mode 100644 (file)
index 0000000..8dd4195
--- /dev/null
@@ -0,0 +1,35 @@
+package net.jcornell.answer_check;
+
+import java.util.ArrayList;
+import java.awt.Component;
+
+import net.jcornell.answer_check.KeyListController.KeyModel;
+import net.jcornell.answer_check.score_entry.ScoreEntryView;
+
+
+public class CompareController {
+       public final KeyModel key;
+       public final CompareScoreEntryController entryController;
+       protected final CompareView view;
+
+       public CompareController(KeyModel key, Component dialogParent) {
+               this.key = key;
+               ScoreEntryView entryView = new CompareEntryView();
+               entryController = new CompareScoreEntryController(
+                       this,
+                       new ArrayList<>(),
+                       entryView
+               );
+               entryView.controller = entryController;
+
+               view = new CompareView(this, dialogParent);
+       }
+
+       public void init() {
+               entryController.init();
+       }
+
+       public void doCompare() {
+               view.doCompare();
+       }
+}
diff --git a/src/net/jcornell/answer_check/CompareScoreEntryController.java b/src/net/jcornell/answer_check/CompareScoreEntryController.java
new file mode 100644 (file)
index 0000000..6102324
--- /dev/null
@@ -0,0 +1,15 @@
+package net.jcornell.answer_check;
+
+import java.util.List;
+import net.jcornell.answer_check.score_entry.ScoreEntryController;
+import net.jcornell.answer_check.score_entry.ScoreEntryView;
+
+
+public class CompareScoreEntryController extends ScoreEntryController {
+       protected final CompareController parent;
+
+       public CompareScoreEntryController(CompareController parent, List<String> contents, ScoreEntryView view) {
+               super(contents, view);
+               this.parent = parent;
+       }
+}
diff --git a/src/net/jcornell/answer_check/CompareView.java b/src/net/jcornell/answer_check/CompareView.java
new file mode 100644 (file)
index 0000000..dce727c
--- /dev/null
@@ -0,0 +1,92 @@
+package net.jcornell.answer_check;
+
+import java.awt.Dimension;
+import java.awt.Color;
+import javax.swing.text.Document;
+import java.util.List;
+import java.awt.Component;
+import javax.swing.text.BadLocationException;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JDialog;
+import javax.swing.JButton;
+import javax.swing.JScrollPane;
+import java.awt.Dialog.ModalityType;
+
+import net.jcornell.answer_check.KeyListController.KeyModel;
+import net.jcornell.answer_check.score_entry.ScoreEntryView;
+import net.jcornell.answer_check.util.GbcBuilder;
+import net.jcornell.answer_check.util.DocumentListenerAdapter;
+
+
+public class CompareView {
+       protected final CompareController controller;
+       protected final Component dialogParent;
+
+       public CompareView(CompareController controller, Component dialogParent) {
+               this.controller = controller;
+               this.dialogParent = dialogParent;
+       }
+
+       public void doCompare() {
+               JButton doneButton = new JButton("Done");
+               JOptionPane optionPane = new JOptionPane(
+                       new JScrollPane(controller.entryController.view.container),
+                       JOptionPane.PLAIN_MESSAGE,
+                       JOptionPane.OK_OPTION,
+                       null,
+                       new JButton[] {doneButton}
+               );
+               JDialog dialog = optionPane.createDialog(dialogParent, "Compare Answers");
+               doneButton.addActionListener(ev -> {
+                       dialog.setVisible(false);
+                       dialog.dispose();
+               });
+
+               dialog.setModalityType(ModalityType.MODELESS);
+               dialog.setResizable(true);
+               dialog.setSize(dialog.getSize().width, 300);
+               dialog.setVisible(true);
+       }
+}
+
+
+class CompareEntryView extends ScoreEntryView {
+       protected static final Color TRANSPARENT = new Color(0, 0, 0, 0);
+
+       @Override
+       protected void finishRow(int ord, Document scoreEntryDoc) {
+               JLabel indicatorLabel = new JLabel();
+               indicatorLabel.setPreferredSize(new Dimension(10, 10));
+               indicatorLabel.setOpaque(true);
+               indicatorLabel.setBorder(javax.swing.BorderFactory.createLineBorder(Color.black));
+
+               scoreEntryDoc.addDocumentListener(new DocumentListenerAdapter(ev -> {
+                       // TODO would be nice to use a generic `CompareEntryController' here instead of casting
+                       List<String> answers = ((CompareScoreEntryController) controller).parent.key.answers;
+                       String found;
+                       try {
+                               found = scoreEntryDoc.getText(0, scoreEntryDoc.getLength());
+                       } catch (BadLocationException e) {
+                               throw new RuntimeException(e);
+                       }
+                       if (ord < answers.size() && !found.isEmpty()) {
+                               String expected = answers.get(ord);
+                               Color color = found.equals(expected) ? Color.green : Color.red;
+                               indicatorLabel.setBackground(color);
+                               indicatorLabel.setOpaque(true);
+                       } else {
+                               indicatorLabel.setOpaque(false);
+                       }
+                       indicatorLabel.repaint();
+               }));
+
+               contentPanel.add(
+                       indicatorLabel,
+                       (new GbcBuilder()
+                               .gridy(ord)
+                               .value
+                       )
+               );
+       }
+}
diff --git a/src/net/jcornell/answer_check/KeyEditModel.java b/src/net/jcornell/answer_check/KeyEditModel.java
new file mode 100644 (file)
index 0000000..2a56a45
--- /dev/null
@@ -0,0 +1,39 @@
+package net.jcornell.answer_check;
+
+import java.awt.Component;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.jcornell.answer_check.KeyListController.KeyModel;
+import net.jcornell.answer_check.score_entry.ScoreEntryController;
+import net.jcornell.answer_check.score_entry.ScoreEntryView;
+
+
+public class KeyEditModel {
+       protected final KeyModel target;
+       protected final ScoreEntryController entryController;
+       protected final KeyEditView view;
+
+       public KeyEditModel(KeyModel target, Component dialogParent) {
+               // Make a copy of the key and turn it over to the editor view.  When the user is done
+               // editing, discard the changes or write them back to the target as appropriate.
+               this.target = target;
+
+               List<String> buffer = new ArrayList<>(target.answers);
+               ScoreEntryView entryView = new ScoreEntryView();
+               entryController = new ScoreEntryController(buffer, entryView);
+               entryView.controller = entryController;
+               entryController.init();
+
+               view = new KeyEditView(this, entryView, dialogParent);
+       }
+
+       public void doEdit() {
+               view.doEdit();
+       }
+
+       public void commit() {
+               target.name = view.getName();
+               target.answers = entryController.cleanContents();
+       }
+}
diff --git a/src/net/jcornell/answer_check/KeyEditView.java b/src/net/jcornell/answer_check/KeyEditView.java
new file mode 100644 (file)
index 0000000..873acb0
--- /dev/null
@@ -0,0 +1,79 @@
+package net.jcornell.answer_check;
+
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.Dimension;
+import javax.swing.JDialog;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+
+import net.jcornell.answer_check.score_entry.ScoreEntryView;
+import net.jcornell.answer_check.util.GbcBuilder;
+
+
+public class KeyEditView {
+       public final KeyEditModel model;
+       protected final ScoreEntryView entryView;
+       protected final JTextField nameField;
+       public final JPanel container;
+       protected final Component dialogParent;
+
+       public KeyEditView(KeyEditModel model, ScoreEntryView entryView, Component dialogParent) {
+               this.model = model;
+               this.entryView = entryView;
+               nameField = new JTextField(model.target.name);
+               container = buildUi();
+               this.dialogParent = dialogParent;
+       }
+
+       protected JPanel buildUi() {
+               JPanel panel = new JPanel();
+               panel.setLayout(new GridBagLayout());
+               JScrollPane entryPanel = new JScrollPane(entryView.container);
+
+               panel.add(
+                       nameField,
+                       (new GbcBuilder()
+                               .fill(GridBagConstraints.HORIZONTAL)
+                               .insets(new Insets(0, 0, 5, 0))
+                               .weightx(1.0)
+                               .value
+                       )
+               );
+               panel.add(
+                       entryPanel,
+                       (new GbcBuilder()
+                               .gridx(0)
+                               .fill(GridBagConstraints.BOTH)
+                               .weightx(1.0)
+                               .weighty(1.0)
+                               .value
+                       )
+               );
+               return panel;
+       }
+
+       public String getName() {
+               return nameField.getText();
+       }
+
+       public void doEdit() {
+               JOptionPane optionPane = new JOptionPane(
+                       container,
+                       JOptionPane.PLAIN_MESSAGE,
+                       JOptionPane.OK_CANCEL_OPTION
+               );
+               JDialog dialog = optionPane.createDialog(dialogParent, "Answer Key Editor");
+               dialog.setResizable(true);
+               dialog.setSize(dialog.getSize().width, 300);
+               dialog.setVisible(true);
+               Integer result = (Integer) optionPane.getValue();
+               if (result != null && result == JOptionPane.OK_OPTION) {
+                       model.commit();
+               }
+       }
+}
diff --git a/src/net/jcornell/answer_check/KeyListController.java b/src/net/jcornell/answer_check/KeyListController.java
new file mode 100644 (file)
index 0000000..963f811
--- /dev/null
@@ -0,0 +1,64 @@
+package net.jcornell.answer_check;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.ArrayList;
+import java.io.Serializable;
+import javax.swing.DefaultListModel;
+
+import net.jcornell.answer_check.score_entry.ScoreEntryController;
+import net.jcornell.answer_check.util.DataSave;
+
+
+public class KeyListController {
+       public static class KeyModel implements Serializable {
+               public String name;
+               public List<String> answers;
+
+               public KeyModel(String name, List<String> answers) {
+                       this.name = name;
+                       this.answers = answers;
+               }
+
+               public String toString() {
+                       return name;
+               }
+       }
+
+       public final DefaultListModel<KeyModel> keys;
+       protected final KeyListView view;
+
+       public KeyListController(List<KeyModel> keys) {
+               this.keys = new DefaultListModel<>();
+               keys.forEach(this.keys::addElement);
+               view = new KeyListView(this);
+       }
+
+       protected void saveData() {
+               DataSave.saveData(Collections.list(keys.elements()));
+       }
+
+       public void addKey() {
+               KeyModel m = new KeyModel("New key", new ArrayList<>());
+               keys.addElement(m);
+               saveData();
+       }
+
+       public void removeKeyAt(int index) {
+               keys.remove(index);
+               saveData();
+       }
+
+       public void doEdit() {
+               KeyEditModel editModel = new KeyEditModel(view.getSelected(), view.container);
+               editModel.doEdit();
+               saveData();
+               view.onEditComplete();
+       }
+
+       public void doCompare() {
+               CompareController compareController = new CompareController(view.getSelected(), view.container);
+               compareController.init();
+               compareController.doCompare();
+       }
+}
diff --git a/src/net/jcornell/answer_check/KeyListView.java b/src/net/jcornell/answer_check/KeyListView.java
new file mode 100644 (file)
index 0000000..27affa6
--- /dev/null
@@ -0,0 +1,102 @@
+package net.jcornell.answer_check;
+
+import java.awt.Container;
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.util.Arrays;
+import javax.swing.GroupLayout.Alignment;
+import javax.swing.GroupLayout;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JButton;
+import javax.swing.ListSelectionModel;
+import javax.swing.JScrollPane;
+import java.awt.Dimension;
+import java.awt.Component;
+import javax.swing.BoxLayout;
+import javax.swing.Box;
+
+import net.jcornell.answer_check.KeyListController.KeyModel;
+import net.jcornell.answer_check.util.GbcBuilder;
+
+
+public class KeyListView {
+       protected final KeyListController controller;
+       public final Container container;
+       protected final JList<KeyModel> list;
+       protected final JButton editBtn, dropBtn, compareBtn;
+
+       public KeyListView(KeyListController controller) {
+               this.controller = controller;
+               list = new JList<>(controller.keys);
+               editBtn = new JButton("Edit…");
+               dropBtn = new JButton("Delete");
+               compareBtn = new JButton("Compare…");
+               container = buildUi();
+               refreshButtonState();
+       }
+
+       protected Container buildUi() {
+               list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+               list.addListSelectionListener(ev -> refreshButtonState());
+               JScrollPane listPane = new JScrollPane(list);
+
+               JButton addBtn = new JButton("Add");
+               addBtn.addActionListener(ev -> {
+                       controller.addKey();
+                       list.setSelectedIndex(controller.keys.size() - 1);
+               });
+
+               editBtn.addActionListener(ev -> controller.doEdit());
+
+               dropBtn.addActionListener(ev -> controller.removeKeyAt(list.getSelectedIndex()));
+
+               compareBtn.addActionListener(ev -> controller.doCompare());
+
+               JPanel buttonBar = new JPanel();
+               Arrays.stream(new Component[] {addBtn, editBtn, dropBtn, compareBtn}).forEach(buttonBar::add);
+
+               JPanel container = new JPanel();
+               GridBagLayout layout = new GridBagLayout();
+               container.setLayout(layout);
+
+               container.add(
+                       listPane,
+                       (
+                               new GbcBuilder()
+                               .gridx(0)
+                               .gridy(0)
+                               .fill(GridBagConstraints.BOTH)
+                               .weightx(1.0)
+                               .weighty(1.0)
+                               .value
+                       )
+               );
+               container.add(
+                       buttonBar,
+                       (
+                               new GbcBuilder()
+                               .gridx(0)
+                               .gridy(1)
+                               .value
+                       )
+               );
+
+               return container;
+       }
+
+       protected void refreshButtonState() {
+               boolean keySelected = list.getSelectedIndex() != -1;
+               for (JButton b : new JButton[] {editBtn, dropBtn, compareBtn}) {
+                       b.setEnabled(keySelected);
+               }
+       }
+
+       protected KeyModel getSelected() {
+               return list.getSelectedValue();
+       }
+
+       public void onEditComplete() {
+               list.repaint();
+       }
+}
diff --git a/src/net/jcornell/answer_check/Main.java b/src/net/jcornell/answer_check/Main.java
new file mode 100644 (file)
index 0000000..5c8a176
--- /dev/null
@@ -0,0 +1,27 @@
+package net.jcornell.answer_check;
+
+import javax.swing.JFrame;
+import java.util.List;
+import java.util.ArrayList;
+
+import net.jcornell.answer_check.KeyListController.KeyModel;
+import net.jcornell.answer_check.util.DataSave;
+
+
+public class Main {
+       public static void main(String[] args) {
+               List<KeyModel> keys;
+               try {
+                       keys = DataSave.loadData();
+               } catch (RuntimeException e) {
+                       keys = new ArrayList<>();
+               }
+
+               KeyListController c = new KeyListController(keys);
+               JFrame frame = new JFrame("Manage Keys");
+               frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+               frame.setContentPane(c.view.container);
+               frame.pack();
+               frame.setVisible(true);
+       }
+}
diff --git a/src/net/jcornell/answer_check/score_entry/ScoreEntryController.java b/src/net/jcornell/answer_check/score_entry/ScoreEntryController.java
new file mode 100644 (file)
index 0000000..c39d6e8
--- /dev/null
@@ -0,0 +1,40 @@
+package net.jcornell.answer_check.score_entry;
+
+import java.util.Stack;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.IntStream;
+import java.util.stream.Collectors;
+
+
+public class ScoreEntryController {
+       public final List<String> contents;
+       public final ScoreEntryView view;
+
+       public ScoreEntryController(List<String> contents, ScoreEntryView view) {
+               this.contents = contents;
+               this.view = view;
+       }
+
+       public void init() {
+               IntStream.range(0, contents.size()).forEach(ord -> view.populateRow(ord));
+               view.init();
+       }
+
+       public void update(int index, String newValue) {
+               if (index < contents.size()) {
+                       contents.set(index, newValue);
+               } else {
+                       assert index == contents.size();
+                       contents.add(newValue);
+               }
+       }
+
+       public List<String> cleanContents() {
+               Stack<String> copy = contents.stream().collect(Collectors.toCollection(Stack::new));
+               while (!copy.isEmpty() && copy.peek().isEmpty()) {
+                       copy.pop();
+               }
+               return copy;
+       }
+}
diff --git a/src/net/jcornell/answer_check/score_entry/ScoreEntryView.java b/src/net/jcornell/answer_check/score_entry/ScoreEntryView.java
new file mode 100644 (file)
index 0000000..8ee9b94
--- /dev/null
@@ -0,0 +1,142 @@
+package net.jcornell.answer_check.score_entry;
+
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+import javax.swing.text.PlainDocument;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.AttributeSet;
+import java.awt.event.ActionListener;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.HashMap;
+import java.util.stream.Collectors;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.text.Document;
+import javax.swing.event.DocumentListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import javax.swing.event.DocumentEvent;
+
+import net.jcornell.answer_check.util.GbcBuilder;
+import net.jcornell.answer_check.util.DocumentListenerAdapter;
+
+
+public class ScoreEntryView {
+       protected static class ScoreDocument extends PlainDocument {
+               @Override
+               public void insertString(int offset, String toInsert, AttributeSet attrs) throws BadLocationException {
+                       if (toInsert != null && getLength() == 0) {
+                               super.insertString(offset, toInsert.toUpperCase(), attrs);
+                       }
+               }
+       }
+
+       protected final FocusListener focusListener = new FocusListener() {
+               @Override public void focusGained(FocusEvent e) {
+                       JTextField target = (JTextField) e.getComponent();
+                       contentPanel.scrollRectToVisible(target.getBounds());
+                       target.selectAll();
+               }
+
+               @Override public void focusLost(FocusEvent e) {}
+       };
+
+       public ScoreEntryController controller;
+       protected final JPanel contentPanel;
+       public final JPanel container;
+       protected final Map<Integer, JTextField> fromOrd;
+       protected final Map<JTextField, Integer> toOrd;
+       protected final Map<Document, JTextField> fieldsByDoc;
+       protected final DocumentListener modelUpdateListener, rowFillListener;
+
+       public ScoreEntryView() {
+               fromOrd = new HashMap<>();
+               toOrd = new HashMap<>();
+               fieldsByDoc = new HashMap<>();
+               modelUpdateListener = new DocumentListenerAdapter(ev -> {
+                       JTextField source = fieldsByDoc.get(ev.getDocument());
+                       controller.update(toOrd.get(source), source.getText());
+               });
+               rowFillListener = new DocumentListenerAdapter(ev -> {
+                       JTextField target = fieldsByDoc.get(ev.getDocument());
+                       int ord = toOrd.get(target);
+                       if (ord == fromOrd.size() - 1) {
+                               addNewRow();
+                       }
+                       if (!target.getText().isEmpty()) {
+                               fromOrd.get(ord + 1).requestFocus();
+                       }
+               });
+
+               container = new JPanel();
+               container.setLayout(new GridBagLayout());
+
+               contentPanel = new JPanel();
+               contentPanel.setLayout(new GridBagLayout());
+
+               container.add(contentPanel, new GridBagConstraints());
+               // add expanding box to push content to the top left
+               container.add(
+                       new JPanel(),
+                       (new GbcBuilder()
+                               .gridx(1)
+                               .gridy(1)
+                               .weightx(1.0)
+                               .weighty(1.0)
+                               .value
+                       )
+               );
+       }
+
+       public void init() {
+               addNewRow();
+       }
+
+       protected void finishRow(int ord, Document scoreEntryDoc) {}
+
+       protected void rowAddImpl(int ord, String initialValue) {
+               JLabel ordLabel = new JLabel(Integer.toString(ord + 1));
+               Document doc = new ScoreDocument();
+               JTextField textBox = new JTextField(doc, initialValue, 2);
+
+               fromOrd.put(ord, textBox);
+               toOrd.put(textBox, ord);
+               fieldsByDoc.put(doc, textBox);
+
+               doc.addDocumentListener(modelUpdateListener);
+               doc.addDocumentListener(rowFillListener);
+               textBox.addFocusListener(focusListener);
+
+               contentPanel.add(
+                       ordLabel,
+                       (new GbcBuilder()
+                               .gridy(ord)
+                               .value
+                       )
+               );
+               contentPanel.add(
+                       textBox,
+                       (new GbcBuilder()
+                               .gridy(ord)
+                               .insets(new Insets(0, 10, 0, 10))
+                               .value
+                       )
+               );
+
+               finishRow(ord, doc);
+               contentPanel.revalidate();
+       }
+
+       protected void addNewRow() {
+               rowAddImpl(fromOrd.size(), "");
+       }
+
+       protected void populateRow(int ord) {
+               rowAddImpl(ord, controller.contents.get(ord));
+       }
+}
diff --git a/src/net/jcornell/answer_check/util/DataSave.java b/src/net/jcornell/answer_check/util/DataSave.java
new file mode 100644 (file)
index 0000000..a60edb4
--- /dev/null
@@ -0,0 +1,44 @@
+package net.jcornell.answer_check.util;
+
+import java.nio.file.Path;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.nio.file.Paths;
+import java.nio.file.Files;
+import java.util.List;
+
+import net.jcornell.answer_check.KeyListController.KeyModel;
+
+
+public class DataSave {
+       public static final Path SAVE_LOCATION = Paths.get(
+               System.getProperty("user.home"), "Library", "Application Support", "answer_check.ser"
+       );
+
+       private DataSave() {}
+
+       public static void saveData(List<KeyModel> data) {
+               try (OutputStream output = Files.newOutputStream(SAVE_LOCATION)) {
+                       new ObjectOutputStream(output).writeObject(data);
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       public static List<KeyModel> loadData() {
+               try {
+                       List<KeyModel> keys;
+                       try (InputStream input = Files.newInputStream(SAVE_LOCATION)) {
+                               @SuppressWarnings("unchecked")
+                               List<KeyModel> keys_ = (List<KeyModel>) new ObjectInputStream(input).readObject();
+                               keys = keys_;
+                       }
+                       return keys;
+               } catch (Exception e) {
+                       throw new RuntimeException(e);
+               }
+       }
+}
diff --git a/src/net/jcornell/answer_check/util/DocumentListenerAdapter.java b/src/net/jcornell/answer_check/util/DocumentListenerAdapter.java
new file mode 100644 (file)
index 0000000..09d4750
--- /dev/null
@@ -0,0 +1,26 @@
+package net.jcornell.answer_check.util;
+
+import java.util.function.Consumer;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+
+public class DocumentListenerAdapter implements DocumentListener {
+       protected final Consumer<DocumentEvent> upstream;
+
+       public DocumentListenerAdapter(Consumer<DocumentEvent> upstream) {
+               this.upstream = upstream;
+       }
+
+       @Override public void changedUpdate(DocumentEvent e) {
+               upstream.accept(e);
+       }
+
+       @Override public void insertUpdate(DocumentEvent e) {
+               upstream.accept(e);
+       }
+
+       @Override public void removeUpdate(DocumentEvent e) {
+               upstream.accept(e);
+       }
+}
diff --git a/src/net/jcornell/answer_check/util/GbcBuilder.java b/src/net/jcornell/answer_check/util/GbcBuilder.java
new file mode 100644 (file)
index 0000000..72e9de9
--- /dev/null
@@ -0,0 +1,44 @@
+package net.jcornell.answer_check.util;
+
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+
+
+public class GbcBuilder {
+       public final GridBagConstraints value = new GridBagConstraints();
+
+       public GbcBuilder gridx(int value) {
+               this.value.gridx = value;
+               return this;
+       }
+
+       public GbcBuilder gridy(int value) {
+               this.value.gridy = value;
+               return this;
+       }
+
+       public GbcBuilder fill(int value) {
+               this.value.fill = value;
+               return this;
+       }
+
+       public GbcBuilder insets(Insets value) {
+               this.value.insets = value;
+               return this;
+       }
+
+       public GbcBuilder anchor(int value) {
+               this.value.anchor = value;
+               return this;
+       }
+
+       public GbcBuilder weightx(double value) {
+               this.value.weightx = value;
+               return this;
+       }
+
+       public GbcBuilder weighty(double value) {
+               this.value.weighty = value;
+               return this;
+       }
+}