001package edu.pdx.cs410J.grader.gradebook.ui;
002
003import edu.pdx.cs410J.ParserException;
004import edu.pdx.cs410J.grader.gradebook.GradeBook;
005import edu.pdx.cs410J.grader.gradebook.Student;
006import edu.pdx.cs410J.grader.gradebook.XmlDumper;
007import edu.pdx.cs410J.grader.gradebook.XmlGradeBookParser;
008
009import javax.swing.*;
010import javax.swing.event.ListSelectionEvent;
011import javax.swing.event.ListSelectionListener;
012import java.awt.*;
013import java.awt.event.ActionEvent;
014import java.awt.event.ActionListener;
015import java.awt.event.WindowAdapter;
016import java.awt.event.WindowEvent;
017import java.io.FileNotFoundException;
018import java.io.IOException;
019import java.util.*;
020
021/**
022 * This panel displays the contents of a grade book.  On the left-hand
023 * side of the panel is a list of the students in the class.  In the
024 * center of the panel is a tabbed pane that lets you view/edit
025 * information about the class, a student, or a student's grades.
026 */
027@SuppressWarnings("serial")
028public class GradeBookPanel extends JPanel {
029  static final int BY_ID = 1;
030  static final int BY_NAME = 2;
031
032  private GradeBook book;  // The GradeBook being displayed
033  private StudentsModel model;
034
035  // GUI components we care about
036  private JList students;
037  private ClassPanel classPanel;
038  private StudentPanel studentPanel;
039  private GradePanel gradePanel;
040
041  /**
042   * Creates a new <code>GradeBookPanel</code> and lays out all of its
043   * components.
044   */
045  public GradeBookPanel(JFrame parent) {
046    this.classPanel = new ClassPanel(parent);
047    this.studentPanel = new StudentPanel();
048    this.gradePanel = new GradePanel();
049
050    JTabbedPane tabs = new JTabbedPane(JTabbedPane.TOP);
051    tabs.addTab("Class", this.classPanel);
052    tabs.addTab("Student", this.studentPanel);
053    tabs.addTab("Grades", this.gradePanel);
054
055    JSplitPane split = new JSplitPane();
056    split.setRightComponent(tabs);
057
058    JPanel leftPanel = new JPanel();
059    leftPanel.setLayout(new BorderLayout());
060    leftPanel.add(new JLabel("Students", JLabel.CENTER),
061                  BorderLayout.NORTH);
062
063    this.students = new JList();
064    this.students.addListSelectionListener(new
065      ListSelectionListener() {
066        public void valueChanged(ListSelectionEvent e) {
067          if (GradeBookPanel.this.model == null) {
068            return;
069          }
070          
071          int index = students.getSelectedIndex();
072
073          if (index >= 0) {
074            String id = model.getIdAt(index);
075            displayStudent(id);
076
077          } else {
078            GradeBookPanel.this.studentPanel.clearContents();
079          }
080        }
081      });
082    this.students.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0));
083
084    leftPanel.add(new JScrollPane(this.students), BorderLayout.CENTER);
085    JPanel buttons = new JPanel();
086    buttons.setLayout(new FlowLayout());
087    ButtonGroup group = new ButtonGroup();
088
089    JRadioButton idButton = new JRadioButton("By id");
090    idButton.addActionListener(new ActionListener() {
091        public void actionPerformed(ActionEvent e) {
092          displayStudents(BY_ID);
093        }
094      });
095    group.add(idButton);
096    buttons.add(idButton);
097
098    JRadioButton nameButton = new JRadioButton("By name");
099    nameButton.addActionListener(new ActionListener() {
100        public void actionPerformed(ActionEvent e) {
101          displayStudents(BY_NAME);
102        }
103      });
104    group.add(nameButton);
105    buttons.add(nameButton);
106    leftPanel.add(buttons, BorderLayout.SOUTH);
107
108    split.setLeftComponent(leftPanel);
109
110    this.setLayout(new BorderLayout());
111    this.add(split, BorderLayout.CENTER);
112  }
113
114  /**
115   * Displays information about a given student
116   */
117  private void displayStudent(String id) {
118    Student student =
119      this.book.getStudent(id).orElseThrow(() -> new IllegalStateException("No student with id " + id));
120    this.studentPanel.displayStudent(student);
121    this.gradePanel.displayStudent(student);
122  }
123
124  /**
125   * Displays the names of the students in the class as either their
126   * full names or by the ids.
127   */
128  private void displayStudents(int sortOrder) {
129    if (this.book == null) {
130      // Nothing to do
131      return;
132    }
133
134    this.model = new StudentsModel(this.book, sortOrder);
135    this.students.setModel(model);
136  }
137
138  /**
139   * Displays the contents of a given <code>GradeBook</code>
140   */
141  void displayGradeBook(GradeBook book) {
142    this.book = book;
143    this.displayStudents(BY_ID);
144    this.classPanel.displayGradeBook(book);
145    this.gradePanel.displayAssignmentsFor(book);
146    this.studentPanel.clearContents();
147  }
148
149  /**
150   * Test program
151   */
152  public static void main(String[] args) {
153    final String fileName = args[0];
154
155    GradeBook book = null;
156    try {
157      XmlGradeBookParser parser = new XmlGradeBookParser(fileName);
158      book = parser.parse();
159
160    } catch (FileNotFoundException ex) {
161      System.err.println("** Could not find file: " + ex.getMessage());
162      System.exit(1);
163      
164    } catch (IOException ex) {
165      System.err.println("** IOException during parsing: " + ex.getMessage());
166      System.exit(1);
167
168    } catch (ParserException ex) {
169      System.err.println("** Error during parsing: " + ex);
170      System.exit(1);
171    }
172
173    JFrame frame = new JFrame("GradeBookPanel test");
174    GradeBookPanel bookPanel = new GradeBookPanel(frame);
175    bookPanel.displayGradeBook(book);
176
177    final GradeBook theBook = book;
178    frame.addWindowListener(new WindowAdapter() {
179        public void windowClosing(WindowEvent e) {
180          // Write changes to grade book back to file
181          try {
182            XmlDumper dumper = new XmlDumper(fileName);
183            dumper.dump(theBook);
184
185          } catch (IOException ex) {
186            System.err.println("** Error while writing XML file: " + ex);
187          }
188
189          System.exit(1);
190        }
191      });
192
193    frame.getContentPane().add(bookPanel);
194    
195    frame.pack();
196    frame.setVisible(true);
197  }
198
199}
200
201/**
202 * Class used for displaying either student ids or their full names in
203 * a <code>JList</code> 
204 */
205@SuppressWarnings("serial")
206class StudentsModel extends AbstractListModel {
207
208  private ArrayList<String> ids;  // ids in order
209  private ArrayList<String> data; // displayed names in order
210
211  public StudentsModel(final GradeBook book, final int sortedOrder) {
212    this.ids = new ArrayList<String>();
213    this.data = new ArrayList<String>();
214
215    SortedSet<Student> sortedStudents = new TreeSet<Student>(new Comparator<Student>() {
216        public int compare(Student s1, Student s2) {
217          if (sortedOrder == GradeBookPanel.BY_ID) {
218            // Sort by id
219            return s1.getId().compareTo(s2.getId());
220
221          } else if (sortedOrder == GradeBookPanel.BY_NAME) {
222            // Sort by last name
223            String name1 = s1.getLastName();
224            String name2 = s2.getLastName();
225
226            if (name1 == null) {
227              name1 = s1.getId();
228            }
229
230            if (name2 == null) {
231              name2 = s2.getId();
232            }
233
234            if (name1.equalsIgnoreCase(name2)) {
235              name1 = s1.getFirstName();
236              name2 = s2.getFirstName();
237
238              if (name1 == null) {
239                name1 = s1.getId();
240              }
241
242              if (name2 == null) {
243                name2 = s2.getId();
244              }
245
246            } else {
247              return name1.compareTo(name2);
248            }
249
250            if (name1.equalsIgnoreCase(name2)) {
251              return s1.getId().compareTo(s2.getId());
252
253            } else {
254              return name1.compareTo(name2);
255            }
256
257          } else {
258            throw new IllegalArgumentException("Unknown sort order: "
259                                               + sortedOrder);
260          }
261        }
262
263        public boolean equals(Object o) {
264          return o == this;
265        }
266      });
267
268    book.forEachStudent(sortedStudents::add);
269
270    for (Student student : sortedStudents) {
271      this.ids.add(student.getId());
272
273      if (sortedOrder == GradeBookPanel.BY_ID) {
274        // Display id
275        this.data.add(student.getId());
276
277      } else if (sortedOrder == GradeBookPanel.BY_NAME) {
278        // Display full name
279        String name = student.getFullName();
280        if (name == null || name.equals("")) {
281          name = student.getId();
282        }
283        this.data.add(name);
284
285      } else {
286        throw new IllegalArgumentException("Unknown sort order: "
287          + sortedOrder);
288      }
289    }
290
291  }
292
293  /**
294   * Returns the id of the student at the given index
295   */
296  public String getIdAt(int index) {
297    return (String) ids.get(index);
298  }
299
300  public Object getElementAt(int index) {
301    return data.get(index);
302  }
303
304  public int getSize() {
305    return this.data.size();
306  }
307
308}