001package edu.pdx.cs410J.family; 002 003import java.awt.event.ActionEvent; 004import java.awt.event.ActionListener; 005import java.awt.event.KeyEvent; 006import java.awt.event.WindowAdapter; 007import java.awt.event.WindowEvent; 008import java.io.File; 009import java.io.FileNotFoundException; 010import java.io.IOException; 011 012import javax.swing.ButtonGroup; 013import javax.swing.JFileChooser; 014import javax.swing.JFrame; 015import javax.swing.JMenu; 016import javax.swing.JMenuBar; 017import javax.swing.JMenuItem; 018import javax.swing.JOptionPane; 019import javax.swing.JRadioButtonMenuItem; 020import javax.swing.KeyStroke; 021import javax.swing.SwingUtilities; 022import javax.swing.UIManager; 023 024/** 025 * This class is a graphical user interface that lets the user edit a 026 * family tree. 027 */ 028@SuppressWarnings("serial") 029public class FamilyTreeGUI extends FamilyTreePanel { 030 private File file; // Where FamilyTree lives 031 private boolean isDirty = false; // Has the family tree been modified? 032 033 // GUI components worth holding onto 034 private JMenuItem saveItem; // Disabled until we have a tree 035 private JMenuItem saveAsItem; // Disabled until we have a tree 036 private JMenu personMenu; // Disabled until a person is selected 037 private JMenuItem motherItem; 038 private JMenuItem fatherItem; 039 private JFrame frame; // Frame containing this GUI 040 041 /** 042 * Creates a new <code>FamilyTreeGUI</code> 043 */ 044 public FamilyTreeGUI(String title) { 045 // Implicit call to super class's constructor 046 super.addComponents(); 047 048 // Create a JFrame 049 this.frame = new JFrame(title); 050 051 // Add the menus 052 JMenuBar menuBar = new JMenuBar(); 053 this.frame.setJMenuBar(menuBar); 054 this.addFileMenu(menuBar); 055 this.addPersonMenu(menuBar); 056 this.addPlafMenu(menuBar); 057 058 // Add myself to the frame 059 System.out.println("Adding this"); 060 this.frame.getContentPane().add(this); 061 062 // Initially, we're not dirty 063 this.setDirty(false); 064 065 // Handle exit events 066 this.frame.addWindowListener(new WindowAdapter() { 067 public void windowClosing(WindowEvent e) { 068 exit(); 069 } 070 }); 071 072 } 073 074 /** 075 * Returns <code>true</code> if this GUI can be used to edit a 076 * family tree. 077 */ 078 boolean canEdit() { 079 return true; 080 } 081 082 /** 083 * Creates the File menu 084 */ 085 private void addFileMenu(JMenuBar menuBar) { 086 JMenu fileMenu = new JMenu("File"); 087 fileMenu.setMnemonic(KeyEvent.VK_F); 088 menuBar.add(fileMenu); 089 090 JMenuItem openItem = new JMenuItem("Open...", KeyEvent.VK_O); 091 openItem.setAccelerator(KeyStroke.getKeyStroke( 092 KeyEvent.VK_O, ActionEvent.CTRL_MASK)); 093 openItem.addActionListener(new ActionListener() { 094 public void actionPerformed(ActionEvent e) { 095 open(); 096 } 097 }); 098 fileMenu.add(openItem); 099 100 this.saveItem = new JMenuItem("Save", KeyEvent.VK_S); 101 saveItem.setAccelerator(KeyStroke.getKeyStroke( 102 KeyEvent.VK_S, ActionEvent.CTRL_MASK)); 103 saveItem.addActionListener(new ActionListener() { 104 public void actionPerformed(ActionEvent e) { 105 save(); 106 } 107 }); 108 fileMenu.add(saveItem); 109 110 this.saveAsItem = new JMenuItem("Save As...", KeyEvent.VK_A); 111 saveAsItem.addActionListener(new ActionListener() { 112 public void actionPerformed(ActionEvent e) { 113 saveAs(); 114 } 115 }); 116 fileMenu.add(saveAsItem); 117 118 fileMenu.addSeparator(); 119 120 JMenuItem exitItem = new JMenuItem("Exit", KeyEvent.VK_X); 121 exitItem.addActionListener(new ActionListener() { 122 public void actionPerformed(ActionEvent e) { 123 exit(); 124 } 125 }); 126 fileMenu.add(exitItem); 127 } 128 129 /** 130 * Creates the Person menu 131 */ 132 private void addPersonMenu(JMenuBar menuBar) { 133 this.personMenu = new JMenu("Person"); 134 personMenu.setMnemonic(KeyEvent.VK_P); 135 personMenu.setEnabled(false); 136 menuBar.add(personMenu); 137 138 fatherItem = new JMenuItem("Father", KeyEvent.VK_F); 139 fatherItem.addActionListener(new ActionListener() { 140 public void actionPerformed(ActionEvent e) { 141 displayFather(); 142 } 143 }); 144 personMenu.add(fatherItem); 145 146 motherItem = new JMenuItem("Mother", KeyEvent.VK_M); 147 motherItem.addActionListener(new ActionListener() { 148 public void actionPerformed(ActionEvent e) { 149 displayMother(); 150 } 151 }); 152 personMenu.add(motherItem); 153 154 personMenu.addSeparator(); 155 156 JMenuItem editItem = new JMenuItem("Edit...", KeyEvent.VK_E); 157 editItem.addActionListener(new ActionListener() { 158 public void actionPerformed(ActionEvent e) { 159 editPerson(); 160 } 161 }); 162 personMenu.add(editItem); 163 164 JMenuItem marriageItem = new JMenuItem("Add Marriage...", 165 KeyEvent.VK_M); 166 marriageItem.addActionListener(new ActionListener() { 167 public void actionPerformed(ActionEvent e) { 168 addMarriage(); 169 } 170 }); 171 personMenu.add(marriageItem); 172 } 173 174 /** 175 * Creates menu that allows the user to change the look and feel of 176 * this <code>FamilyTreeGUI</code>. 177 */ 178 private void addPlafMenu(JMenuBar menuBar) { 179 JMenu plafMenu = new JMenu("Look & Feel"); 180 plafMenu.setMnemonic(KeyEvent.VK_L); 181 menuBar.add(plafMenu); 182 183 ButtonGroup bg = new ButtonGroup(); 184 185 UIManager.LookAndFeelInfo[] infos = 186 UIManager.getInstalledLookAndFeels(); 187 for (int i = 0; i < infos.length; i++) { 188 final UIManager.LookAndFeelInfo info = infos[i]; 189 190 JRadioButtonMenuItem plafItem; 191 if (info.getName().equals(UIManager.getLookAndFeel().getName())) 192 { 193 plafItem = new JRadioButtonMenuItem(info.getName(), true); 194 195 } else { 196 plafItem = new JRadioButtonMenuItem(info.getName(), false); 197 } 198 199 plafItem.addActionListener(new ActionListener() { 200 public void actionPerformed(ActionEvent e) { 201 try { 202 UIManager.setLookAndFeel(info.getClassName()); 203 SwingUtilities.updateComponentTreeUI(FamilyTreeGUI.this); 204 FamilyTreeGUI.this.pack(); 205 206 } catch (Exception ex) { 207 error(ex.toString()); 208 return; 209 } 210 } 211 }); 212 213 bg.add(plafItem); 214 plafMenu.add(plafItem); 215 } 216 } 217 218 /** 219 * Pops up a dialog box that notifies the user of a non-fatal 220 * error. 221 */ 222 private void error(String message) { 223 JOptionPane.showMessageDialog(this, new String[] { 224 message}, 225 "An error has occurred", 226 JOptionPane.ERROR_MESSAGE); 227 } 228 229 /** 230 * Brings up a dialog box that edits the current <code>Person</code> 231 */ 232 void editPerson() { 233 Person person = this.treeList.getSelectedPerson(); 234 if (person == null) { 235 return; 236 } 237 238 EditPersonDialog dialog = 239 new EditPersonDialog(person, this.getFrame(), this.tree); 240 dialog.pack(); 241 dialog.setLocationRelativeTo(this); 242 dialog.setVisible(true); 243 244 person = dialog.getPerson(); 245 if (person != null) { 246 // Assume some change was made 247 this.setDirty(true); 248 249 this.showPerson(person); 250 this.treeList.fillInList(this.tree); 251 } 252 } 253 254 /** 255 * Sets the <code>Person</code> displayed in the GUI 256 */ 257 void showPerson(Person person) { 258 if (person == null) { 259 this.personMenu.setEnabled(false); 260 261 } else { 262 this.personMenu.setEnabled(true); 263 264 // Can we enable the Mother and Father menus? 265 this.motherItem.setEnabled(person.getMother() != null); 266 this.fatherItem.setEnabled(person.getFather() != null); 267 } 268 269 this.personPanel.showPerson(person); 270 } 271 272 /** 273 * Brings up a dialog box for editing the a new <code>Person</code> 274 */ 275 void newPerson() { 276 EditPersonDialog dialog = 277 new EditPersonDialog(this.getFrame(), this.tree); 278 dialog.pack(); 279 dialog.setLocationRelativeTo(this); 280 dialog.setVisible(true); 281 282 Person newPerson = dialog.getPerson(); 283 if (newPerson != null) { 284 // A change was made 285 this.setDirty(true); 286 287 this.tree.addPerson(newPerson); 288 this.treeList.fillInList(this.tree); 289 } 290 } 291 292 /** 293 * Brings up a dialog box for noting the current 294 * <code>Person</code>'s marriage. 295 */ 296 void addMarriage() { 297 Person person = this.treeList.getSelectedPerson(); 298 299 EditMarriageDialog dialog = 300 new EditMarriageDialog(person, this.getFrame(), this.tree); 301 dialog.pack(); 302 dialog.setLocationRelativeTo(this); 303 dialog.setVisible(true); 304 305 Marriage newMarriage = dialog.getMarriage(); 306 if (newMarriage != null) { 307 // A change was made and update person panel 308 this.setDirty(true); 309 this.showPerson(person); 310 } 311 } 312 313 /** 314 * Saves the family tree to a file 315 */ 316 private void save() { 317 if (this.file == null) { 318 saveAs(); 319 return; 320 } 321 322// System.out.println("Saving to an XML file"); 323 324 try { 325 XmlDumper dumper = new XmlDumper(this.file); 326 dumper.dump(this.tree); 327 this.setDirty(false); 328 329 } catch (IOException ex) { 330 error("Error while saving family tree: " + ex); 331 } 332 333 } 334 335 /** 336 * Creates a <code>JFileChooser</code> suitable for dealing with 337 * text files. 338 */ 339 private JFileChooser getFileChooser() { 340 JFileChooser chooser = new JFileChooser(); 341 if (this.file == null) { 342 String cwd = System.getProperty("user.dir"); 343 chooser.setCurrentDirectory(new File(cwd)); 344 345 } else { 346 chooser.setCurrentDirectory(this.file.getParentFile()); 347 } 348 349 chooser.setFileFilter(new javax.swing.filechooser.FileFilter() { 350 public boolean accept(File file) { 351 if (file.isDirectory()) { 352 return true; 353 } 354 355 String fileName = file.getName(); 356 return fileName.endsWith(".xml"); 357 } 358 359 public String getDescription() { 360 return "XML files (*.xml)"; 361 } 362 }); 363 chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); 364 chooser.setMultiSelectionEnabled(false); 365 366 return chooser; 367 } 368 369 /** 370 * Displays a dialog box that allows the user to select an XML file 371 * to save the family tree to. 372 */ 373 private void saveAs() { 374// System.out.println("Saving as..."); 375 376 JFileChooser chooser = this.getFileChooser(); 377 chooser.setDialogTitle("Save As..."); 378 chooser.setDialogType(JFileChooser.SAVE_DIALOG); 379 int response = chooser.showSaveDialog(this); 380 381 if (response == JFileChooser.APPROVE_OPTION) { 382 this.file = chooser.getSelectedFile(); 383 384 if (this.file.exists()) { 385 response = 386 JOptionPane.showConfirmDialog(this, new String[] { 387 "File " + this.file + " already exists.", 388 "Do you want to overwrite it?"}, 389 "Overwrite file?", 390 JOptionPane.YES_NO_OPTION, 391 JOptionPane.QUESTION_MESSAGE); 392 393 if (response == JOptionPane.NO_OPTION) { 394 // Try it again... 395 saveAs(); 396 return; // Only save once 397 } 398 } 399 400 save(); 401 } 402 } 403 404 /** 405 * Pops open a dialog box for choosing an XML file 406 */ 407 private void open() { 408// System.out.println("Opening an XML file"); 409 410 if (this.isDirty) { 411 int response = 412 JOptionPane.showConfirmDialog(this, new String[] { 413 "You have made changes to your family tree.", 414 "Do you want to save them?"}, 415 "Confirm changes", 416 JOptionPane.YES_NO_CANCEL_OPTION, 417 JOptionPane.QUESTION_MESSAGE); 418 419 if (response == JOptionPane.YES_OPTION) { 420 save(); 421 422 } else if (response == JOptionPane.CANCEL_OPTION) { 423 // Don't open new file, keep working with old 424 return; 425 } 426 427 // Otherwise, discard changes and open new file 428 } 429 430 JFileChooser chooser = this.getFileChooser(); 431 chooser.setDialogTitle("Open text file"); 432 chooser.setDialogType(JFileChooser.OPEN_DIALOG); 433 int response = chooser.showOpenDialog(this); 434 435 if (response == JFileChooser.APPROVE_OPTION) { 436 File file = chooser.getSelectedFile(); 437 FamilyTree tree = null; 438 439 try { 440 XmlParser parser = new XmlParser(file); 441 tree = parser.parse(); 442 443 } catch (FileNotFoundException ex) { 444 error(ex.toString()); 445 446 } catch (FamilyTreeException ex) { 447 error(ex.toString()); 448 } 449 450 451 if (tree != null) { 452 // Everything is okay 453 this.file = file; 454 this.sourceLocation.setText(this.file.getName()); 455 this.tree = tree; 456 this.setDirty(false); 457 458 this.treeList.fillInList(this.tree); 459 this.sourceLocation.setText(this.file.getPath()); 460 } 461 } 462 } 463 464 /** 465 * Called when the family tree changes dirtiness 466 */ 467 void setDirty(boolean isDirty) { 468 this.isDirty = isDirty; 469 this.saveAsItem.setEnabled(isDirty); 470 this.saveItem.setEnabled(isDirty); 471 } 472 473 /** 474 * Returns the <code>JFrame</code> associated with this GUI. 475 */ 476 JFrame getFrame() { 477 return this.frame; 478 } 479 480 /** 481 * If the family tree has been modified and not saved, prompt the 482 * user to verify that he really wants to exit. 483 */ 484 private void exit() { 485 if (this.isDirty) { 486 int response = 487 JOptionPane.showConfirmDialog(this, new String[] { 488 "You have made changes to your family tree.", 489 "Do you want to save them?"}, 490 "Confirm changes", 491 JOptionPane.YES_NO_CANCEL_OPTION, 492 JOptionPane.QUESTION_MESSAGE); 493 494 if (response == JOptionPane.YES_OPTION) { 495 save(); 496 System.exit(0); 497 498 } else if (response == JOptionPane.NO_OPTION) { 499 System.exit(0); 500 } 501 502 // Otherwise continue working 503 504 } else { 505 System.exit(0); 506 } 507 } 508 509 /** 510 * Overridden to pack the containing <code>JFrame</code>. 511 */ 512 public void pack() { 513 this.frame.pack(); 514 } 515 516 /** 517 * Overridden to set the visibility of the containing 518 * <code>JFrame</code>. 519 */ 520 public void setVisible(boolean isVisible) { 521 this.frame.setVisible(isVisible); 522 } 523 524 /** 525 * Creates a <code>FamilyTreeGUI</code> 526 */ 527 public static void main(String[] args) { 528 FamilyTreeGUI gui = new FamilyTreeGUI("Family Tree Program"); 529 gui.pack(); 530 gui.setVisible(true); 531 } 532 533}