package ui; import dao.MarkingProperties; import java.awt.Dimension; import java.awt.Font; import model.CriterionEditor; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.SwingUtilities; import model.Criterion; /** * * @author Mark George <mark.george@otago.ac.nz> */ public class CriterionPanel extends JPanel implements CriterionEditor { private RadioGroup radioGroup = null; private final JTextArea txtComment; private final Criterion criterion; private final JButton clearBtn; private JTextField txtNumeric = null; private static Dimension selectedSize; @SuppressWarnings("Convert2Lambda") public CriterionPanel(Criterion criterion) { this.criterion = criterion; this.setLayout(new GridBagLayout()); // label is left aligned and full width GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 3; gbc.weightx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(0, 0, 5, 0); this.add(new JLabel(criterion.getDescription()), gbc); // clear mark buttonleft aligned, but doesn't grow gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 1; clearBtn = new JButton("X"); this.add(clearBtn, gbc); // mark component is left aligned, but doesn't grow gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 1; if (criterion.isScale()) { radioGroup = new RadioGroup(criterion.getMin(), criterion.getMax()); this.add(radioGroup, gbc); } else { gbc.insets.left = 20; gbc.insets.right = 20; gbc.fill = GridBagConstraints.HORIZONTAL; txtNumeric = new JTextField(); txtNumeric.setToolTipText("Out of " + criterion.getMax()); this.add(txtNumeric, gbc); } // text area is right aligned, but grows in both directions gbc = new GridBagConstraints(); gbc.gridx = 2; gbc.gridy = 1; gbc.weightx = 1; gbc.weighty = 1; gbc.fill = GridBagConstraints.BOTH; txtComment = new JTextArea(); txtComment.setWrapStyleWord(true); txtComment.setLineWrap(true); JScrollPane scrollPane = new JScrollPane(); scrollPane.setViewportView(txtComment); scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); // Dimensions are necessary to prevent the semi-famous "text area grows infinitely" bug. // Unfortunately this also means we can't let the layout manager figure out the correct // size dynamically... txtComment.setMinimumSize(new Dimension(10, 20)); txtComment.setPreferredSize(new Dimension(10, 100)); // need to hardcode height which is annoying... scrollPane.setPreferredSize(new Dimension(10, 20)); txtComment.addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent fe) { super.focusGained(fe); CriterionPanel selectedPanel = CriterionPanel.this; MarkingFrame frame = (MarkingFrame) SwingUtilities.getWindowAncestor(selectedPanel); if (frame.getExpandable()) { selectedPanel.setPreferredSize(new Dimension(10, 150)); selectedPanel.invalidate(); SwingUtilities.getWindowAncestor(selectedPanel).pack(); } } @Override public void focusLost(FocusEvent fe) { super.focusLost(fe); CriterionPanel selectedPanel = CriterionPanel.this; MarkingFrame frame = (MarkingFrame) SwingUtilities.getWindowAncestor(selectedPanel); if (frame.getExpandable()) { selectedPanel.setPreferredSize(selectedSize); selectedPanel.invalidate(); SwingUtilities.getWindowAncestor(selectedPanel).pack(); } } }); clearBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { if (criterion.isScale()) { radioGroup.clearSelection(); } else { txtNumeric.setText(""); } } }); Font font = txtComment.getFont(); Font biggerFont = font.deriveFont((float)MarkingProperties.textAreaFontSize()); txtComment.setFont(biggerFont); this.add(scrollPane, gbc); } @Override public String getComment() { return txtComment.getText().trim(); } @Override // TODO: switch to BigDeciamal public Double getResult() { if (criterion.isScale()) { Integer result = radioGroup.getSelected(); return result != null ? Double.valueOf(result) : null; } else { try { String val = txtNumeric.getText(); if (val.isBlank()) { // mark not entered, so return null return null; } Double result = Double.valueOf(val); if (result < criterion.getMin() || result > criterion.getMax()) { JOptionPane.showMessageDialog(null, txtNumeric.getText() + " is not within the exected range.\n\nMinimum: " + criterion.getMin() + "\nMaximum: " + criterion.getMax(), "Invalid Number", JOptionPane.ERROR_MESSAGE); throw new RuntimeException("Number is not within expected range"); } return Double.valueOf(txtNumeric.getText()); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(null, txtNumeric.getText() + " is not a valid number", "Invalid Number", JOptionPane.ERROR_MESSAGE); throw new RuntimeException(ex.getLocalizedMessage()); } } } @Override public boolean isComplete() { return getResult() != null; } @Override public void setComment(String comment) { txtComment.setText(comment); // scroll to top txtComment.setCaretPosition(0); } @Override public void setResult(Double result) { if (criterion.isScale()) { radioGroup.setSelected(result.intValue()); } else { txtNumeric.setText(String.valueOf(result)); } } public void setZero() { setResult(0.0); } public void setMax() { setResult(Double.valueOf(criterion.getMax())); } public Criterion getCriterion() { return criterion; } }