+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));
+ }
+}