package edu.pdx.cs410J.grader; import edu.pdx.cs410J.ParserException; import javax.swing.*; import javax.swing.border.Border; import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.FileNotFoundException; import java.io.IOException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.*; import java.util.List; /** * This panel is used to display and edit a <code>Student</code>'s * <code>Grade</code>s. */ @SuppressWarnings("serial") public class GradePanel extends JPanel { private Student student; private GradeBook book; // GUI components we care about private JLabel studentNameLabel; private JList<String> assignmentsList; private JLabel gradeLabel; private JLabel assignmentLabel; private JTextField gradeField; private NotesPanel notes; private JList<String> lateList; private JList<String> resubmitList; private JList<LocalDateTime> submissionTimesList; /** The most recently selected index in the assignments list */ private int assignmentIndex = -1; /////////////////////// Constructors /////////////////////// /** * Creates and populates a new <code>GradePanel</code> */ public GradePanel() { this.setLayout(new BorderLayout()); this.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); this.add(createStudentNamePanel(), BorderLayout.NORTH); JSplitPane centerPanel = new JSplitPane(); centerPanel.setLeftComponent(createAssignmentListPanel()); JPanel mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); mainPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); mainPanel.add(createGradeDetailPanel(), BorderLayout.CENTER); JPanel bottomPanel = new JPanel(); bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS)); bottomPanel.add(createLatePanel()); bottomPanel.add(createResubmitPanel()); mainPanel.add(bottomPanel, BorderLayout.SOUTH); centerPanel.setRightComponent(mainPanel); this.add(centerPanel, BorderLayout.CENTER); } private JPanel createStudentNamePanel() { JPanel studentNamePanel = new JPanel(); studentNamePanel.setLayout(new BoxLayout(studentNamePanel, BoxLayout.X_AXIS)); studentNamePanel.add(Box.createHorizontalGlue()); this.studentNameLabel = new JLabel("Student"); studentNamePanel.add(this.studentNameLabel); studentNamePanel.add(Box.createHorizontalGlue()); return studentNamePanel; } private JPanel createAssignmentListPanel() { JPanel listPanel = new JPanel(); Border assignmentBorder = BorderFactory.createTitledBorder("Assignments"); listPanel.setBorder(assignmentBorder); listPanel.setLayout(new BorderLayout()); this.assignmentsList = new JList<>(); assignmentsList.addListSelectionListener(e -> { Assignment assign = getSelectedAssignment(); if (assign != null) { displayGradeFor(assign); } }); listPanel.add(new JScrollPane(assignmentsList), BorderLayout.CENTER); return listPanel; } private JPanel createResubmitPanel() { JPanel resubmitPanel = new JPanel(); resubmitPanel.setBorder(BorderFactory.createTitledBorder("Resubmitted")); resubmitPanel.setLayout(new BorderLayout()); this.resubmitList = new JList<>(); resubmitPanel.add(new JScrollPane(this.resubmitList), BorderLayout.CENTER); JPanel resubmitButtons = new JPanel(); resubmitButtons.setLayout(new FlowLayout()); JButton addResubmit = new JButton("Add"); addResubmit.addActionListener(e -> { Assignment assign = getSelectedAssignment(); if (assign != null && student != null) { student.addResubmitted(assign.getName()); Vector<String> v = new Vector<>(student.getResubmitted()); resubmitList.setListData(v); } }); resubmitButtons.add(addResubmit); resubmitPanel.add(resubmitButtons, BorderLayout.SOUTH); return resubmitPanel; } private JPanel createLatePanel() { JPanel latePanel = new JPanel(); latePanel.setBorder(BorderFactory.createTitledBorder("Late")); latePanel.setLayout(new BorderLayout()); this.lateList = new JList<>(); latePanel.add(new JScrollPane(this.lateList), BorderLayout.CENTER); JPanel lateButtons = new JPanel(); lateButtons.setLayout(new FlowLayout()); JButton addLate = new JButton("Add"); addLate.addActionListener(e -> { Assignment assign = getSelectedAssignment(); if (assign != null && student != null) { student.addLate(assign.getName()); Vector<String> v = new Vector<>(student.getLate()); lateList.setListData(v); } }); lateButtons.add(addLate); latePanel.add(lateButtons, BorderLayout.SOUTH); return latePanel; } private JPanel createGradeDetailPanel() { JPanel gradePanel = new JPanel(); gradePanel.setLayout(new BorderLayout()); gradePanel.setBorder(BorderFactory.createTitledBorder("Grade")); gradePanel.add(createGradeInfoPanel(), BorderLayout.NORTH); this.notes = new NotesPanel(); gradePanel.add(this.notes, BorderLayout.CENTER); gradePanel.add(createSubmissionsList(), BorderLayout.EAST); gradePanel.add(createGradeButtonsPanel(), BorderLayout.SOUTH); return gradePanel; } private Component createSubmissionsList() { JPanel submissionsPanel = new JPanel(); submissionsPanel.setLayout(new BorderLayout()); submissionsPanel.setBorder(BorderFactory.createTitledBorder("Submissions")); this.submissionTimesList = new JList<>(); this.submissionTimesList.setCellRenderer(new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent(JList<?> list, Object dateTime, int index, boolean isSelected, boolean cellHasFocus) { LocalDateTime submissionTime = (LocalDateTime) dateTime; String value = submissionTime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)); return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); } }); submissionsPanel.add(new JScrollPane(this.submissionTimesList), BorderLayout.CENTER); return submissionsPanel; } private JPanel createGradeButtonsPanel() { JPanel buttons = new JPanel(); buttons.setLayout(new FlowLayout()); JButton updateButton = new JButton("Update"); updateButton.addActionListener(e -> createOrUpdateGrade()); buttons.add(updateButton); return buttons; } private JPanel createGradeInfoPanel() { JPanel p = new JPanel(); p.setLayout(new BorderLayout()); this.assignmentLabel = new JLabel("Assignment"); p.add(this.assignmentLabel, BorderLayout.NORTH); JPanel labels = new JPanel(); labels.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2)); labels.setLayout(new GridLayout(0, 1)); this.gradeLabel = new JLabel("Grade (out of ):", JLabel.CENTER); labels.add(this.gradeLabel); JPanel fields = new JPanel(); fields.setLayout(new GridLayout(0, 1)); this.gradeField = new JTextField(5); this.gradeField.addActionListener(e -> createOrUpdateGrade()); fields.add(this.gradeField); p.add(labels, BorderLayout.WEST); p.add(fields, BorderLayout.CENTER); return p; } private void createOrUpdateGrade() { Assignment assign = getSelectedAssignment(); if (assign != null && student != null) { Grade grade = student.getGrade(assign.getName()); if (grade == null) { createGrade(); } else { updateGrade(grade); } } } /** * Displays the assignments for a given <code>GradeBook</code> */ public void displayAssignmentsFor(GradeBook book) { this.book = book; // Clear grade fields Vector<String> names = new Vector<>(book.getAssignmentNames()); this.assignmentsList.setListData(names); this.assignmentLabel.setText("Assignment"); this.gradeLabel.setText("Grade (out of ):"); this.gradeField.setText(""); } /** * Displays the grades for a given <code>Student</code> */ public void displayStudent(Student student) { // Clear any grade info this.assignmentLabel.setText("Assignment"); this.gradeLabel.setText("Grade (out of ):"); this.gradeField.setText(""); this.notes.clearNotes(); this.student = student; String name = student.getFullName(); if (name.equals("")) { name = student.getId(); } this.studentNameLabel.setText(name + " (" + student.getId() + ")"); this.lateList.setListData(new Vector<>(student.getLate())); this.resubmitList.setListData(new Vector<>(student.getResubmitted())); // Might as well refresh assignments list this.displayAssignmentsFor(this.book); // Display the grade for the currently selected Assignment assign = getSelectedAssignment(); if (assign != null) { displayGradeFor(assign); } } /** * Displays the current students grade for a given assignment */ private void displayGradeFor(Assignment assign) { this.assignmentLabel.setText("Assignment " + assign.getName() + " (" + assign.getDescription() + ")"); this.gradeLabel.setText("Grade (out of " + assign.getPoints() + "):"); if (this.student != null) { Grade grade = student.getGrade(assign.getName()); if (grade != null) { this.gradeField.setText(String.valueOf(grade.getScore())); this.notes.setNotable(grade); this.submissionTimesList.setModel(createListModel(grade.getSubmissionTimes())); return; } } this.gradeField.setText(""); this.notes.clearNotes(); } private ListModel<LocalDateTime> createListModel(List<LocalDateTime> submissionTimes) { return new AbstractListModel<LocalDateTime>() { @Override public int getSize() { return submissionTimes.size(); } @Override public LocalDateTime getElementAt(int index) { return submissionTimes.get(index); } }; } /** * Returns the <code>Assignment</code> that is currently selected in * the assignmentsList. */ private Assignment getSelectedAssignment() { String name = this.assignmentsList.getSelectedValue(); if (name != null) { this.assignmentIndex = this.assignmentsList.getSelectedIndex(); return this.book.getAssignment(name); } else if (this.assignmentIndex == -1) { return null; } else if (this.assignmentIndex >= this.assignmentsList.getModel().getSize()) { // Changed grade book? this.assignmentIndex = -1; return null; } else { this.assignmentsList.setSelectedIndex(this.assignmentIndex); return getSelectedAssignment(); } } /** * Creates a new <code>Grade</code> based on the contents of this * <code>GradePanel</code> */ private Grade createGrade() { Assignment assign = getSelectedAssignment(); if (this.student == null) { return null; } else if (assign == null) { String s = "Please select an assignment"; return error(s); } String score = this.gradeField.getText(); try { double d = Double.parseDouble(score); Grade grade = new Grade(assign.getName(), d); this.notes.setNotable(grade); this.student.setGrade(assign.getName(), grade); return grade; } catch (NumberFormatException ex) { return error(score + " is not a number"); } } /** * Updates the current <code>Grade</code> based on the contents of * this <code>GradePanel</code> */ private void updateGrade(Grade grade) { String score = this.gradeField.getText(); try { // All we can change is the score double d = Double.parseDouble(score); grade.setScore(d); } catch (NumberFormatException ex) { error(score + " is not a number"); } // Remember that the NotesPanel automatically updates the grade } private <T> T error(String message) { JOptionPane.showMessageDialog(this, new String[]{message}, "Error", JOptionPane.ERROR_MESSAGE); return null; } /** * Test program */ public static void main(String[] args) { final String fileName = args[0]; String studentName = args[1]; GradeBook book = null; try { XmlGradeBookParser parser = new XmlGradeBookParser(fileName); book = parser.parse(); } catch (FileNotFoundException ex) { System.err.println("** Could not find file: " + ex.getMessage()); System.exit(1); } catch (IOException ex) { System.err.println("** IOException during parsing: " + ex.getMessage()); System.exit(1); } catch (ParserException ex) { System.err.println("** Error during parsing: " + ex); System.exit(1); } JFrame frame = new JFrame("GradePanel test"); GradePanel gradePanel = new GradePanel(); gradePanel.displayAssignmentsFor(book); Student student = book.getStudent(studentName).orElseThrow(() -> new IllegalStateException("No student with id " + studentName)); gradePanel.displayStudent(student); final GradeBook theBook = book; frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { // Write changes to grade book back to file try { XmlDumper dumper = new XmlDumper(fileName); dumper.dump(theBook); } catch (IOException ex) { System.err.println("** Error while writing XML file: " + ex); } System.exit(1); } }); frame.getContentPane().add(gradePanel); frame.pack(); frame.setVisible(true); } }