001package edu.pdx.cs410J.grader.gradebook.ui;
002
003import edu.pdx.cs410J.ParserException;
004import edu.pdx.cs410J.grader.gradebook.*;
005
006import javax.swing.*;
007import javax.swing.border.Border;
008import java.awt.*;
009import java.awt.event.WindowAdapter;
010import java.awt.event.WindowEvent;
011import java.io.FileNotFoundException;
012import java.io.IOException;
013import java.time.LocalDateTime;
014import java.time.format.DateTimeFormatter;
015import java.time.format.FormatStyle;
016import java.util.*;
017import java.util.List;
018
019/**
020 * This panel is used to display and edit a <code>Student</code>'s
021 * <code>Grade</code>s.
022 */
023@SuppressWarnings("serial")
024public class GradePanel extends JPanel {
025
026  private Student student;
027  private GradeBook book;
028
029  // GUI components we care about
030  private JLabel studentNameLabel;
031  private JList<String> assignmentsList;
032  private JLabel gradeLabel;
033  private JLabel assignmentLabel;
034  private JTextField gradeField;
035  private NotesPanel notes;
036  private JList<String> lateList;
037  private JList<String> resubmitList;
038  private JList<LocalDateTime> submissionTimesList;
039
040  /** The most recently selected index in the assignments list */
041  private int assignmentIndex = -1;
042
043  ///////////////////////  Constructors  ///////////////////////
044
045  /**
046   * Creates and populates a new <code>GradePanel</code>
047   */
048  public GradePanel() {
049    this.setLayout(new BorderLayout());
050    this.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
051
052    this.add(createStudentNamePanel(), BorderLayout.NORTH);
053
054    JSplitPane centerPanel = new JSplitPane();
055
056    centerPanel.setLeftComponent(createAssignmentListPanel());
057
058    JPanel mainPanel = new JPanel();
059    mainPanel.setLayout(new BorderLayout());
060    mainPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
061
062    mainPanel.add(createGradeDetailPanel(), BorderLayout.CENTER);
063
064    JPanel bottomPanel = new JPanel();
065    bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS));
066
067    bottomPanel.add(createLatePanel());
068    bottomPanel.add(createResubmitPanel());
069    
070    mainPanel.add(bottomPanel, BorderLayout.SOUTH);
071
072    centerPanel.setRightComponent(mainPanel);
073
074    this.add(centerPanel, BorderLayout.CENTER);
075  }
076
077  private JPanel createStudentNamePanel() {
078    JPanel studentNamePanel = new JPanel();
079    studentNamePanel.setLayout(new BoxLayout(studentNamePanel,
080                                             BoxLayout.X_AXIS));
081    studentNamePanel.add(Box.createHorizontalGlue());
082    this.studentNameLabel = new JLabel("Student");
083    studentNamePanel.add(this.studentNameLabel);
084    studentNamePanel.add(Box.createHorizontalGlue());
085    return studentNamePanel;
086  }
087
088  private JPanel createAssignmentListPanel() {
089    JPanel listPanel = new JPanel();
090    Border assignmentBorder =
091      BorderFactory.createTitledBorder("Assignments");
092    listPanel.setBorder(assignmentBorder);
093    listPanel.setLayout(new BorderLayout());
094
095    this.assignmentsList = new JList<>();
096    assignmentsList.addListSelectionListener(e -> {
097      Assignment assign = getSelectedAssignment();
098      if (assign != null) {
099        displayGradeFor(assign);
100      }
101    });
102    listPanel.add(new JScrollPane(assignmentsList),
103                  BorderLayout.CENTER);
104    return listPanel;
105  }
106
107  private JPanel createResubmitPanel() {
108    JPanel resubmitPanel = new JPanel();
109    resubmitPanel.setBorder(BorderFactory.createTitledBorder("Resubmitted"));
110    resubmitPanel.setLayout(new BorderLayout());
111    this.resubmitList = new JList<>();
112    resubmitPanel.add(new JScrollPane(this.resubmitList), BorderLayout.CENTER);
113    JPanel resubmitButtons = new JPanel();
114    resubmitButtons.setLayout(new FlowLayout());
115    JButton addResubmit = new JButton("Add");
116    addResubmit.addActionListener(e -> {
117      Assignment assign = getSelectedAssignment();
118      if (assign != null && student != null) {
119          student.addResubmitted(assign.getName());
120          Vector<String> v = new Vector<>(student.getResubmitted());
121          resubmitList.setListData(v);
122        }
123    });
124    resubmitButtons.add(addResubmit);
125    resubmitPanel.add(resubmitButtons, BorderLayout.SOUTH);
126    return resubmitPanel;
127  }
128
129  private JPanel createLatePanel() {
130    JPanel latePanel = new JPanel();
131    latePanel.setBorder(BorderFactory.createTitledBorder("Late"));
132    latePanel.setLayout(new BorderLayout());
133    this.lateList = new JList<>();
134    latePanel.add(new JScrollPane(this.lateList), BorderLayout.CENTER);
135    JPanel lateButtons = new JPanel();
136    lateButtons.setLayout(new FlowLayout());
137    JButton addLate = new JButton("Add");
138    addLate.addActionListener(e -> {
139      Assignment assign = getSelectedAssignment();
140      if (assign != null && student != null) {
141          student.addLate(assign.getName());
142          Vector<String> v = new Vector<>(student.getLate());
143          lateList.setListData(v);
144      }
145    });
146    lateButtons.add(addLate);
147    latePanel.add(lateButtons, BorderLayout.SOUTH);
148    return latePanel;
149  }
150
151  private JPanel createGradeDetailPanel() {
152    JPanel gradePanel = new JPanel();
153    gradePanel.setLayout(new BorderLayout());
154    gradePanel.setBorder(BorderFactory.createTitledBorder("Grade"));
155
156    gradePanel.add(createGradeInfoPanel(), BorderLayout.NORTH);
157
158    this.notes = new NotesPanel();
159    gradePanel.add(this.notes, BorderLayout.CENTER);
160
161    gradePanel.add(createSubmissionsList(), BorderLayout.EAST);
162
163    gradePanel.add(createGradeButtonsPanel(), BorderLayout.SOUTH);
164
165    return gradePanel;
166  }
167
168  private Component createSubmissionsList() {
169    JPanel submissionsPanel = new JPanel();
170    submissionsPanel.setLayout(new BorderLayout());
171    submissionsPanel.setBorder(BorderFactory.createTitledBorder("Submissions"));
172
173    this.submissionTimesList = new JList<>();
174    this.submissionTimesList.setCellRenderer(new DefaultListCellRenderer() {
175      @Override
176      public Component getListCellRendererComponent(JList<?> list, Object dateTime, int index, boolean isSelected, boolean cellHasFocus) {
177        LocalDateTime submissionTime = (LocalDateTime) dateTime;
178        String value = submissionTime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT));
179        return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
180      }
181    });
182
183    submissionsPanel.add(new JScrollPane(this.submissionTimesList), BorderLayout.CENTER);
184
185    return submissionsPanel;
186  }
187
188  private JPanel createGradeButtonsPanel() {
189    JPanel buttons = new JPanel();
190    buttons.setLayout(new FlowLayout());
191    JButton updateButton = new JButton("Update");
192    updateButton.addActionListener(e -> createOrUpdateGrade());
193    buttons.add(updateButton);
194    return buttons;
195  }
196
197  private JPanel createGradeInfoPanel() {
198    JPanel p = new JPanel();
199    p.setLayout(new BorderLayout());
200
201    this.assignmentLabel = new JLabel("Assignment");
202    p.add(this.assignmentLabel, BorderLayout.NORTH);
203
204    JPanel labels = new JPanel();
205    labels.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
206    labels.setLayout(new GridLayout(0, 1));
207    this.gradeLabel = new JLabel("Grade (out of ):", JLabel.CENTER);
208    labels.add(this.gradeLabel);
209
210    JPanel fields = new JPanel();
211    fields.setLayout(new GridLayout(0, 1));
212    this.gradeField = new JTextField(5);
213    this.gradeField.addActionListener(e -> createOrUpdateGrade());
214    fields.add(this.gradeField);
215
216    p.add(labels, BorderLayout.WEST);
217    p.add(fields, BorderLayout.CENTER);
218    return p;
219  }
220
221  private void createOrUpdateGrade() {
222    Assignment assign = getSelectedAssignment();
223    if (assign != null && student != null) {
224      Grade grade = student.getGrade(assign.getName());
225      if (grade == null) {
226        createGrade();
227
228      } else {
229        updateGrade(grade);
230      }
231    }
232  }
233
234  /**
235   * Displays the assignments for a given <code>GradeBook</code>
236   */
237  public void displayAssignmentsFor(GradeBook book) {
238    this.book = book;
239
240    // Clear grade fields
241    Vector<String> names = new Vector<>(book.getAssignmentNames());
242    this.assignmentsList.setListData(names);
243
244    this.assignmentLabel.setText("Assignment");
245    this.gradeLabel.setText("Grade (out of ):");
246    this.gradeField.setText("");
247  }
248
249  /**
250   * Displays the grades for a given <code>Student</code>
251   */
252  public void displayStudent(Student student) {
253    // Clear any grade info
254    this.assignmentLabel.setText("Assignment");
255    this.gradeLabel.setText("Grade (out of ):");
256    this.gradeField.setText("");
257    this.notes.clearNotes();
258
259    this.student = student;
260    String name = student.getFullName();
261    if (name.equals("")) {
262      name = student.getId();
263    }
264    this.studentNameLabel.setText(name + " (" + student.getId() + ")");
265    this.lateList.setListData(new Vector<>(student.getLate()));
266    this.resubmitList.setListData(new Vector<>(student.getResubmitted()));
267
268    // Might as well refresh assignments list
269    this.displayAssignmentsFor(this.book);
270
271    // Display the grade for the currently selected
272    Assignment assign = getSelectedAssignment();
273    if (assign != null) {
274      displayGradeFor(assign);
275    }
276  }
277
278  /**
279   * Displays the current students grade for a given assignment
280   */
281  private void displayGradeFor(Assignment assign) {
282    this.assignmentLabel.setText("Assignment " + assign.getName() +
283                                 " (" + assign.getDescription() + ")");
284    this.gradeLabel.setText("Grade (out of " + assign.getPoints() +
285                            "):");
286    
287    if (this.student != null) {
288      Grade grade = student.getGrade(assign.getName());
289      if (grade != null) {
290        this.gradeField.setText(String.valueOf(grade.getScore()));
291        this.notes.setNotable(grade);
292        this.submissionTimesList.setModel(createListModel(grade.getSubmissionTimes()));
293        return;
294      }
295    }
296
297    this.gradeField.setText("");
298    this.notes.clearNotes();
299  }
300
301  private ListModel<LocalDateTime> createListModel(List<LocalDateTime> submissionTimes) {
302    return new AbstractListModel<LocalDateTime>() {
303      @Override
304      public int getSize() {
305        return submissionTimes.size();
306      }
307
308      @Override
309      public LocalDateTime getElementAt(int index) {
310        return submissionTimes.get(index);
311      }
312    };
313  }
314
315  /**
316   * Returns the <code>Assignment</code> that is currently selected in
317   * the assignmentsList.
318   */
319  private Assignment getSelectedAssignment() {
320    String name = this.assignmentsList.getSelectedValue();
321    if (name != null) {
322      this.assignmentIndex = this.assignmentsList.getSelectedIndex();
323      return this.book.getAssignment(name);
324
325    } else if (this.assignmentIndex == -1) {
326      return null;
327
328    } else if (this.assignmentIndex >=
329               this.assignmentsList.getModel().getSize()) {
330      // Changed grade book?
331      this.assignmentIndex = -1;
332      return null;
333
334    } else {
335      this.assignmentsList.setSelectedIndex(this.assignmentIndex);
336      return getSelectedAssignment();
337    }
338  }
339
340  /**
341   * Creates a new <code>Grade</code> based on the contents of this
342   * <code>GradePanel</code>
343   */
344  private Grade createGrade() {
345    Assignment assign = getSelectedAssignment();
346
347    if (this.student == null) {
348      return null;
349
350    } else if (assign == null) {
351      String s = "Please select an assignment";
352      return error(s);
353    }
354
355    String score = this.gradeField.getText();
356
357    try {
358      double d = Double.parseDouble(score);
359      Grade grade = new Grade(assign.getName(), d);
360      this.notes.setNotable(grade);
361      this.student.setGrade(assign.getName(), grade);
362      return grade;
363
364    } catch (NumberFormatException ex) {
365      return error(score + " is not a number");
366    }
367  }
368
369  /**
370   * Updates the current <code>Grade</code> based on the contents of
371   * this <code>GradePanel</code>
372   */
373  private void updateGrade(Grade grade) {
374    String score = this.gradeField.getText();
375    try {
376      // All we can change is the score
377      double d = Double.parseDouble(score);
378      grade.setScore(d);
379
380    } catch (NumberFormatException ex) {
381      error(score + " is not a number");
382    }
383
384    // Remember that the NotesPanel automatically updates the grade
385  }
386
387  private <T> T error(String message) {
388    JOptionPane.showMessageDialog(this,
389      new String[]{message},
390      "Error",
391      JOptionPane.ERROR_MESSAGE);
392    return null;
393  }
394
395  /**
396   * Test program
397   */
398  public static void main(String[] args) {
399    final String fileName = args[0];
400    String studentName = args[1];
401
402    GradeBook book = null;
403    try {
404      XmlGradeBookParser parser = new XmlGradeBookParser(fileName);
405      book = parser.parse();
406
407    } catch (FileNotFoundException ex) {
408      System.err.println("** Could not find file: " + ex.getMessage());
409      System.exit(1);
410      
411    } catch (IOException ex) {
412      System.err.println("** IOException during parsing: " + ex.getMessage());
413      System.exit(1);
414
415    } catch (ParserException ex) {
416      System.err.println("** Error during parsing: " + ex);
417      System.exit(1);
418    }
419
420    JFrame frame = new JFrame("GradePanel test");
421    GradePanel gradePanel = new GradePanel();
422    gradePanel.displayAssignmentsFor(book);
423
424    Student student =
425      book.getStudent(studentName).orElseThrow(() -> new IllegalStateException("No student with id " + studentName));
426    gradePanel.displayStudent(student);
427
428    final GradeBook theBook = book;
429    frame.addWindowListener(new WindowAdapter() {
430        @Override
431        public void windowClosing(WindowEvent e) {
432          // Write changes to grade book back to file
433          try {
434            XmlDumper dumper = new XmlDumper(fileName);
435            dumper.dump(theBook);
436
437          } catch (IOException ex) {
438            System.err.println("** Error while writing XML file: " + ex);
439          }
440
441          System.exit(1);
442        }
443      });
444
445    frame.getContentPane().add(gradePanel);
446    
447    frame.pack();
448    frame.setVisible(true);
449  }
450}