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}