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.*; 010import java.io.File; 011import java.io.IOException; 012import java.io.PrintStream; 013import java.net.URL; 014import java.util.ArrayList; 015import java.util.TimerTask; 016import java.util.prefs.Preferences; 017 018/** 019 * This class is a main GUI for manipulate the grade book for CS410J. 020 * 021 * @author David Whitlock 022 * @version $Revision: 1.15 $ 023 * @since Fall 2000 024 */ 025@SuppressWarnings("serial") 026public class GradeBookGUI extends JFrame { 027 028 /** The prefix for the "recent file" preference */ 029 private static final String RECENT_FILE = "GradeBook_recent_file_"; 030 031 /** The maximum number of recent files */ 032 private static final int MAX_RECENT_FILES = 4; 033 034 /////////////////////// Instance Fields /////////////////////// 035 036 private GradeBook book; 037 private GradeBookPanel bookPanel; 038 private File file; // Where the grade book lives 039 040 /** The most recently visited files (by name) */ 041 private ArrayList<String> recentFiles; 042 043 /** Preferences for using this application */ 044 private Preferences prefs; 045 046 /** A menu listing the recently visited gradebook files */ 047 private JMenu recentFilesMenu; 048 049 /** A label that displays status messages */ 050 private JLabel status; 051 052 /** An action for creating a new grade book file */ 053 private Action newAction; 054 055 /** An action for importing students into a gradebook */ 056 private Action importAction; 057 058 /** An action for opening a gradebook */ 059 private Action openAction; 060 061 /** An action for saving a gradebook */ 062 private Action saveAction; 063 064 /** An action for saving a gradebook with a given file name */ 065 private Action saveAsAction; 066 067 /** An action for exiting the GUI */ 068 private Action exitAction; 069 070 /////////////////////// Constructors /////////////////////// 071 072 /** 073 * Create and lay out a new <code>GradeBookGUI</code> 074 */ 075 public GradeBookGUI(String title) { 076 super(title); 077 078 // Read preference information 079 this.prefs = Preferences.userNodeForPackage(this.getClass()); 080 this.recentFiles = new ArrayList<String>(); 081 for (int i = 0; i < MAX_RECENT_FILES; i++) { 082 String recent = this.prefs.get(RECENT_FILE + i, null); 083 if (recent != null) { 084 File file = new File(recent); 085 if (file.exists() && file.isFile()) { 086 this.recentFiles.add(0, recent); 087 } 088 } 089 } 090 091 // Set up the actions 092 this.setupActions(); 093 094 // Add the menus 095 JMenuBar menuBar = new JMenuBar(); 096 this.setJMenuBar(menuBar); 097 this.addFileMenu(menuBar); 098 this.updateRecentFilesMenu(); 099 100 this.getContentPane().setLayout(new BorderLayout()); 101 102 // Add the tool bar 103 this.getContentPane().add(this.createToolBar(), 104 BorderLayout.NORTH); 105 106 // Add the GradeBookPanel 107 this.bookPanel = new GradeBookPanel(this); 108 this.getContentPane().add(this.bookPanel, BorderLayout.CENTER); 109 110 // Add the status label. Status messages are displaed for five 111 // seconds 112 this.status = new JLabel(" ") { 113 private java.util.Timer timer = 114 new java.util.Timer(true /* isDaemon */); 115 private TimerTask lastTask = null; 116 117 public void setText(String text) { 118 super.setText(text); 119 if ("".equals(text.trim())) { 120 // Avoid recusion 121 return; 122 } 123 124 if (lastTask != null) { 125 lastTask.cancel(); 126 } 127 lastTask = new TimerTask() { 128 public void run() { 129 SwingUtilities.invokeLater(new Runnable() { 130 public void run() { 131 // Empty label looks funny with border 132 GradeBookGUI.this.status.setText(" "); 133 } 134 }); 135 } 136 }; 137 timer.schedule(lastTask, 5 * 1000); 138 } 139 }; 140 Border inside = BorderFactory.createEmptyBorder(0, 2, 0, 0); 141 Border outside = BorderFactory.createLoweredBevelBorder(); 142 Border border = 143 BorderFactory.createCompoundBorder(outside, inside); 144 this.status.setBorder(border); 145 this.getContentPane().add(this.status, BorderLayout.SOUTH); 146 147 // Handle exit events 148 this.addWindowListener(new WindowAdapter() { 149 public void windowClosing(WindowEvent e) { 150 exit(); 151 } 152 }); 153 } 154 155 /** 156 * Initializes the action objects that encapsulate actions that can 157 * be performed by various widgets in this GUI. 158 */ 159 private void setupActions() { 160 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 161 162 // New action 163 this.newAction = new AbstractAction("New ...") { 164 public void actionPerformed(ActionEvent e) { 165 newGradeBook(); 166 } 167 }; 168 this.newAction.putValue(Action.SHORT_DESCRIPTION, 169 "New grade book"); 170 this.newAction.putValue(Action.LONG_DESCRIPTION, 171 "Create a new grade book"); 172 this.newAction.putValue(Action.ACCELERATOR_KEY, 173 KeyStroke.getKeyStroke("control N")); 174 this.newAction.putValue(Action.MNEMONIC_KEY, 175 new Integer(KeyEvent.VK_N)); 176 URL newURL = 177 cl.getResource("toolbarButtonGraphics/general/New16.gif"); 178 if (newURL != null) { 179 this.newAction.putValue(Action.SMALL_ICON, new ImageIcon(newURL)); 180 } 181 182 // Open action 183 this.openAction = new AbstractAction("Open ...") { 184 public void actionPerformed(ActionEvent e) { 185 open(); 186 } 187 }; 188 this.openAction.putValue(Action.SHORT_DESCRIPTION, 189 "Open grade book"); 190 this.openAction.putValue(Action.LONG_DESCRIPTION, 191 "Open a grade book file"); 192 this.openAction.putValue(Action.ACCELERATOR_KEY, 193 KeyStroke.getKeyStroke("control O")); 194 this.openAction.putValue(Action.MNEMONIC_KEY, 195 new Integer(KeyEvent.VK_O)); 196 URL openURL = 197 cl.getResource("toolbarButtonGraphics/general/Open16.gif"); 198 if (openURL != null) { 199 this.openAction.putValue(Action.SMALL_ICON, new ImageIcon(openURL)); 200 } 201 202 // Import action 203 this.importAction = new AbstractAction("Import...") { 204 public void actionPerformed(ActionEvent e) { 205 importStudent(); 206 } 207 }; 208 this.importAction.putValue(Action.SHORT_DESCRIPTION, 209 "Import students"); 210 this.importAction.putValue(Action.LONG_DESCRIPTION, 211 "Import students into a grade book"); 212 this.importAction.putValue(Action.ACCELERATOR_KEY, 213 KeyStroke.getKeyStroke("control I")); 214 this.importAction.putValue(Action.MNEMONIC_KEY, 215 new Integer(KeyEvent.VK_I)); 216 this.importAction.setEnabled(false); 217 URL importURL = 218 cl.getResource("toolbarButtonGraphics/general/Import16.gif"); 219 if (importURL != null) { 220 this.importAction.putValue(Action.SMALL_ICON, new ImageIcon(importURL)); 221 } 222 223 // Save action 224 this.saveAction = new AbstractAction("Save") { 225 public void actionPerformed(ActionEvent e) { 226 save(); 227 } 228 }; 229 this.saveAction.putValue(Action.SHORT_DESCRIPTION, 230 "Save grade book"); 231 this.saveAction.putValue(Action.LONG_DESCRIPTION, 232 "Save the current grade book file"); 233 this.saveAction.putValue(Action.ACCELERATOR_KEY, 234 KeyStroke.getKeyStroke("control S")); 235 this.saveAction.putValue(Action.MNEMONIC_KEY, 236 new Integer(KeyEvent.VK_S)); 237 this.saveAction.setEnabled(false); 238 URL saveURL = 239 cl.getResource("toolbarButtonGraphics/general/Save16.gif"); 240 if (saveURL != null) { 241 this.saveAction.putValue(Action.SMALL_ICON, new ImageIcon(saveURL)); 242 } 243 244 // Save As action 245 this.saveAsAction = new AbstractAction("Save As ...") { 246 public void actionPerformed(ActionEvent e) { 247 saveAs(); 248 } 249 }; 250 this.saveAsAction.putValue(Action.SHORT_DESCRIPTION, 251 "Save grade book"); 252 this.saveAsAction.putValue(Action.MNEMONIC_KEY, 253 new Integer(KeyEvent.VK_A)); 254 this.saveAsAction.setEnabled(false); 255 URL saveAsURL = 256 cl.getResource("toolbarButtonGraphics/general/SaveAs16.gif"); 257 if (saveAsURL != null) { 258 this.saveAsAction.putValue(Action.SMALL_ICON, new ImageIcon(saveAsURL)); 259 } 260 261 // Exit Action 262 this.exitAction = new AbstractAction("Exit") { 263 public void actionPerformed(ActionEvent e) { 264 exit(); 265 } 266 }; 267 this.exitAction.putValue(Action.SHORT_DESCRIPTION, "Exit"); 268 this.exitAction.putValue(Action.LONG_DESCRIPTION, 269 "Exit the grade book program"); 270 this.exitAction.putValue(Action.ACCELERATOR_KEY, 271 KeyStroke.getKeyStroke("control Q")); 272 this.exitAction.putValue(Action.MNEMONIC_KEY, 273 new Integer(KeyEvent.VK_X)); 274 } 275 276 /** 277 * Returns a <code>JToolBar</code> for this GUI 278 */ 279 private JToolBar createToolBar() { 280 JToolBar tools = new JToolBar(JToolBar.HORIZONTAL); 281 tools.setFloatable(false); 282 tools.add(this.newAction); 283 tools.add(this.openAction); 284 tools.add(this.importAction); 285 tools.addSeparator(); 286 tools.add(this.saveAction); 287 tools.add(this.saveAsAction); 288 return tools; 289 } 290 291 /** 292 * Adds a "File" menu to this gui 293 */ 294 private void addFileMenu(JMenuBar menuBar) { 295 JMenu fileMenu = new JMenu("File"); 296 fileMenu.setMnemonic(KeyEvent.VK_F); 297 menuBar.add(fileMenu); 298 299 fileMenu.add(new JMenuItem(this.newAction)); 300 fileMenu.add(new JMenuItem(this.openAction)); 301 fileMenu.add(new JMenuItem(this.importAction)); 302 303 fileMenu.addSeparator(); 304 305 fileMenu.add(new JMenuItem(this.saveAction)); 306 fileMenu.add(new JMenuItem(this.saveAsAction)); 307 308 fileMenu.addSeparator(); 309 310 this.recentFilesMenu = new JMenu("Recent Files"); 311 this.recentFilesMenu.setMnemonic(KeyEvent.VK_R); 312 fileMenu.add(this.recentFilesMenu); 313 314 fileMenu.addSeparator(); 315 316 fileMenu.add(new JMenuItem(this.exitAction)); 317 } 318 319 /** 320 * Displays a dialog box alerting the user that an error has 321 * occurred. 322 */ 323 private void error(String message) { 324 JOptionPane.showMessageDialog(this, message, 325 "An error occurred", 326 JOptionPane.ERROR_MESSAGE); 327 } 328 329 /** 330 * Displays a dialog box alerting the user that an exception was 331 * thrown 332 */ 333 private void error(String message, Exception ex) { 334 JOptionPane.showMessageDialog(this, new String[] { 335 message, ex.getMessage()}, 336 "An exception was thrown", 337 JOptionPane.ERROR_MESSAGE); 338 } 339 340 /** 341 * Makes note that the current <code>file</code> has been changed. 342 * Updates the preferences accordingly. 343 * 344 * @since Fall 2004 345 */ 346 private void noteRecentFile() { 347 assert this.file != null; 348 String name = this.file.getAbsolutePath(); 349 this.recentFiles.remove(name); 350 this.recentFiles.add(0, name); 351 while (this.recentFiles.size() > MAX_RECENT_FILES) { 352 this.recentFiles.remove(this.recentFiles.size() - 1); 353 } 354 for (int i = 0; 355 i < this.recentFiles.size() && i < MAX_RECENT_FILES; 356 i++) { 357 this.prefs.put(RECENT_FILE + i, 358 (String) this.recentFiles.get(i)); 359 } 360 updateRecentFilesMenu(); 361 } 362 363 /** 364 * Updates the contents of the "recent files" menu 365 * 366 * @since Fall 2004 367 */ 368 private void updateRecentFilesMenu() { 369 this.recentFilesMenu.removeAll(); 370 JMenuItem clear = new JMenuItem("Clear recent files"); 371 clear.addActionListener(new ActionListener() { 372 public void actionPerformed(ActionEvent e) { 373 GradeBookGUI.this.recentFiles.clear(); 374 updateRecentFilesMenu(); 375 } 376 }); 377 this.recentFilesMenu.add(clear); 378 this.recentFilesMenu.addSeparator(); 379 380 for (int i = 0; i < this.recentFiles.size(); i++) { 381 String fileName = (String) this.recentFiles.get(i); 382 final File file = new File(fileName); 383 if (file.exists()) { 384 JMenuItem item = new JMenuItem((i+1) + ": " + fileName); 385 item.addActionListener(new ActionListener() { 386 public void actionPerformed(ActionEvent e) { 387 open(file); 388 } 389 }); 390 this.recentFilesMenu.add(item); 391 } 392 } 393 } 394 395 /** 396 * Saves the grade book being edited to a file 397 */ 398 private void save() { 399 if (this.book == null) { 400 // Nothing to do 401 return; 402 } 403 404 if (this.file == null) { 405 saveAs(); 406 // saveAs() will recursively invoke save() if the user selects a 407 // file 408 return; 409 } 410 411 // Dump grade to file as XML 412 try { 413 XmlDumper dumper = new XmlDumper(this.file); 414 dumper.dump(this.book); 415 this.status.setText("Saved " + this.file); 416 417 } catch (IOException ex) { 418 error("While writing grade book", ex); 419 return; 420 } 421 } 422 423 /** 424 * Creates a <code>JFileChooser</code> suitable for dealing with 425 * xml files. 426 */ 427 private JFileChooser getFileChooser() { 428 JFileChooser chooser = new JFileChooser(); 429 if (this.file != null) { 430 chooser.setCurrentDirectory(file.getAbsoluteFile().getParentFile()); 431 } else { 432 String cwd = System.getProperty("user.dir"); 433 chooser.setCurrentDirectory(new File(cwd)); 434 } 435 436 chooser.setFileFilter(new javax.swing.filechooser.FileFilter() { 437 public boolean accept(File file) { 438 if (file.isDirectory()) { 439 return true; 440 } 441 442 String fileName = file.getName(); 443 return fileName.endsWith(".xml"); 444 } 445 446 public String getDescription() { 447 return "XML files"; 448 } 449 }); 450 chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); 451 chooser.setMultiSelectionEnabled(false); 452 453 return chooser; 454 } 455 456 /** 457 * Prompts the user for a file to save the grade book in. 458 */ 459 private void saveAs() { 460 JFileChooser chooser = this.getFileChooser(); 461 chooser.setDialogTitle("Save as..."); 462 chooser.setDialogType(JFileChooser.SAVE_DIALOG); 463 int response = chooser.showSaveDialog(this); 464 465 if (response == JFileChooser.APPROVE_OPTION) { 466 this.file = chooser.getSelectedFile(); 467 save(); 468 noteRecentFile(); 469 } 470 } 471 472 /** 473 * Prompts the user for a file to display 474 */ 475 private void open() { 476 JFileChooser chooser = this.getFileChooser(); 477 chooser.setDialogTitle("Open grade book file"); 478 chooser.setDialogType(JFileChooser.OPEN_DIALOG); 479 int response = chooser.showOpenDialog(this); 480 481 if (response == JFileChooser.APPROVE_OPTION) { 482 // Read the grade book from the file and display it in the GUI 483 File file = chooser.getSelectedFile(); 484 open(file); 485 } 486 } 487 488 /** 489 * Opens the grade book stored in the given file 490 */ 491 private void open(File file) { 492 this.checkDirtyGradeBook(); 493 494 try { 495 XmlGradeBookParser parser = new XmlGradeBookParser(file); 496 GradeBook book = parser.parse(); 497 this.displayGradeBook(book); 498 this.file = file; 499 noteRecentFile(); 500 501 } catch (IOException ex) { 502 error("While parsing file " + file.getName(), ex); 503 return; 504 505 } catch (ParserException ex) { 506 error("While parsing file " + file.getName(), ex); 507 } 508 509 this.status.setText("Opened " + file); 510 } 511 512 /** 513 * Prompts the user for a file from which a student is read and 514 * added to the grade book 515 */ 516 private void importStudent() { 517 if (this.book == null) { 518 error("You must open a grade book before importing a student"); 519 return; 520 } 521 522 JFileChooser chooser = this.getFileChooser(); 523 chooser.setDialogTitle("Import student XML file"); 524 chooser.setDialogType(JFileChooser.OPEN_DIALOG); 525 chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); 526 chooser.setMultiSelectionEnabled(true); 527 int response = chooser.showOpenDialog(this); 528 529 if (response == JFileChooser.APPROVE_OPTION) { 530 // Read the student from each of the selected files and add it 531 // to the grade book 532 File[] files = chooser.getSelectedFiles(); 533 for (int i = 0; i < files.length; i++) { 534 File file = files[i]; 535 try { 536 XmlStudentParser sp = new XmlStudentParser(file); 537 Student student = sp.parseStudent(); 538 this.book.addStudent(student); 539 this.status.setText("Imported " + student.getFullName()); 540 541 } catch (IOException ex) { 542 error("Could not access " + file.getName(), ex); 543 return; 544 545 } catch (ParserException ex) { 546 error("While parsing file " + file.getName(), ex); 547 return; 548 } 549 } 550 } 551 } 552 553 /** 554 * Creates a new grade book to be edited 555 */ 556 private void newGradeBook() { 557 // Prompt user for name of grade book 558 String className = 559 JOptionPane.showInputDialog(this, 560 "Enter the name of the class", 561 "Enter class name", 562 JOptionPane.INFORMATION_MESSAGE); 563 564 if (className == null || className.equals("")) { 565 return; 566 } 567 568 this.displayGradeBook(new GradeBook(className)); 569 this.file = null; 570 this.status.setText("Created new graded book for \"" + className + 571 "\""); 572 } 573 574 /** 575 * Prompts the user to save modified data and exits 576 */ 577 private void exit() { 578 checkDirtyGradeBook(); 579 System.exit(0); 580 } 581 582 /** 583 * If the grade book has been modified and not saved, ask the user 584 * if he wants to save it. 585 */ 586 private void checkDirtyGradeBook() { 587 if (this.book != null && this.book.isDirty()) { 588 int response = JOptionPane.showConfirmDialog(this, new String[] { 589 "You have made changes to the grade book.", 590 "Do you want to save them?"}, 591 "Save Changes?", 592 JOptionPane.YES_NO_CANCEL_OPTION, 593 JOptionPane.QUESTION_MESSAGE); 594 595 if (response == JOptionPane.YES_OPTION) { 596 save(); 597 598 } else if (response == JOptionPane.CANCEL_OPTION) { 599 // Don't exit 600 return; 601 } 602 } 603 } 604 605 /** 606 * Displays a <code>GradeBook</code> in this GUI 607 */ 608 private void displayGradeBook(GradeBook book) { 609 this.book = book; 610 this.importAction.setEnabled(true); 611 this.saveAction.setEnabled(true); 612 this.saveAsAction.setEnabled(true); 613 this.setTitle(book.getClassName()); 614 this.bookPanel.displayGradeBook(this.book); 615 } 616 617 /** 618 * Main program. Create and show a <code>GradeBookGUI</code> 619 */ 620 public static void main(String[] args) { 621 GradeBookGUI gui = new GradeBookGUI("CS410J Grade Book Program"); 622 623 if (args.length > 0) { 624 PrintStream err = System.err; 625 File file = new File(args[0]); 626 if (!file.exists()) { 627 err.println("** " + file + " does not exist"); 628 629 } else if (!file.isFile()) { 630 err.println("** " + file + " is not a file"); 631 632 } else { 633 gui.open(file); 634 } 635 } 636 637 gui.pack(); 638 gui.setVisible(true); 639 } 640 641}