package edu.pdx.cs410J.family; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import javax.swing.ButtonGroup; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JRadioButtonMenuItem; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.UIManager; /** * This class is a graphical user interface that lets the user edit a * family tree. */ @SuppressWarnings("serial") public class FamilyTreeGUI extends FamilyTreePanel { private File file; // Where FamilyTree lives private boolean isDirty = false; // Has the family tree been modified? // GUI components worth holding onto private JMenuItem saveItem; // Disabled until we have a tree private JMenuItem saveAsItem; // Disabled until we have a tree private JMenu personMenu; // Disabled until a person is selected private JMenuItem motherItem; private JMenuItem fatherItem; private JFrame frame; // Frame containing this GUI /** * Creates a new <code>FamilyTreeGUI</code> */ public FamilyTreeGUI(String title) { // Implicit call to super class's constructor super.addComponents(); // Create a JFrame this.frame = new JFrame(title); // Add the menus JMenuBar menuBar = new JMenuBar(); this.frame.setJMenuBar(menuBar); this.addFileMenu(menuBar); this.addPersonMenu(menuBar); this.addPlafMenu(menuBar); // Add myself to the frame System.out.println("Adding this"); this.frame.getContentPane().add(this); // Initially, we're not dirty this.setDirty(false); // Handle exit events this.frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { exit(); } }); } /** * Returns <code>true</code> if this GUI can be used to edit a * family tree. */ boolean canEdit() { return true; } /** * Creates the File menu */ private void addFileMenu(JMenuBar menuBar) { JMenu fileMenu = new JMenu("File"); fileMenu.setMnemonic(KeyEvent.VK_F); menuBar.add(fileMenu); JMenuItem openItem = new JMenuItem("Open...", KeyEvent.VK_O); openItem.setAccelerator(KeyStroke.getKeyStroke( KeyEvent.VK_O, ActionEvent.CTRL_MASK)); openItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { open(); } }); fileMenu.add(openItem); this.saveItem = new JMenuItem("Save", KeyEvent.VK_S); saveItem.setAccelerator(KeyStroke.getKeyStroke( KeyEvent.VK_S, ActionEvent.CTRL_MASK)); saveItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { save(); } }); fileMenu.add(saveItem); this.saveAsItem = new JMenuItem("Save As...", KeyEvent.VK_A); saveAsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { saveAs(); } }); fileMenu.add(saveAsItem); fileMenu.addSeparator(); JMenuItem exitItem = new JMenuItem("Exit", KeyEvent.VK_X); exitItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { exit(); } }); fileMenu.add(exitItem); } /** * Creates the Person menu */ private void addPersonMenu(JMenuBar menuBar) { this.personMenu = new JMenu("Person"); personMenu.setMnemonic(KeyEvent.VK_P); personMenu.setEnabled(false); menuBar.add(personMenu); fatherItem = new JMenuItem("Father", KeyEvent.VK_F); fatherItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { displayFather(); } }); personMenu.add(fatherItem); motherItem = new JMenuItem("Mother", KeyEvent.VK_M); motherItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { displayMother(); } }); personMenu.add(motherItem); personMenu.addSeparator(); JMenuItem editItem = new JMenuItem("Edit...", KeyEvent.VK_E); editItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { editPerson(); } }); personMenu.add(editItem); JMenuItem marriageItem = new JMenuItem("Add Marriage...", KeyEvent.VK_M); marriageItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { addMarriage(); } }); personMenu.add(marriageItem); } /** * Creates menu that allows the user to change the look and feel of * this <code>FamilyTreeGUI</code>. */ private void addPlafMenu(JMenuBar menuBar) { JMenu plafMenu = new JMenu("Look & Feel"); plafMenu.setMnemonic(KeyEvent.VK_L); menuBar.add(plafMenu); ButtonGroup bg = new ButtonGroup(); UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels(); for (int i = 0; i < infos.length; i++) { final UIManager.LookAndFeelInfo info = infos[i]; JRadioButtonMenuItem plafItem; if (info.getName().equals(UIManager.getLookAndFeel().getName())) { plafItem = new JRadioButtonMenuItem(info.getName(), true); } else { plafItem = new JRadioButtonMenuItem(info.getName(), false); } plafItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { UIManager.setLookAndFeel(info.getClassName()); SwingUtilities.updateComponentTreeUI(FamilyTreeGUI.this); FamilyTreeGUI.this.pack(); } catch (Exception ex) { error(ex.toString()); return; } } }); bg.add(plafItem); plafMenu.add(plafItem); } } /** * Pops up a dialog box that notifies the user of a non-fatal * error. */ private void error(String message) { JOptionPane.showMessageDialog(this, new String[] { message}, "An error has occurred", JOptionPane.ERROR_MESSAGE); } /** * Brings up a dialog box that edits the current <code>Person</code> */ void editPerson() { Person person = this.treeList.getSelectedPerson(); if (person == null) { return; } EditPersonDialog dialog = new EditPersonDialog(person, this.getFrame(), this.tree); dialog.pack(); dialog.setLocationRelativeTo(this); dialog.setVisible(true); person = dialog.getPerson(); if (person != null) { // Assume some change was made this.setDirty(true); this.showPerson(person); this.treeList.fillInList(this.tree); } } /** * Sets the <code>Person</code> displayed in the GUI */ void showPerson(Person person) { if (person == null) { this.personMenu.setEnabled(false); } else { this.personMenu.setEnabled(true); // Can we enable the Mother and Father menus? this.motherItem.setEnabled(person.getMother() != null); this.fatherItem.setEnabled(person.getFather() != null); } this.personPanel.showPerson(person); } /** * Brings up a dialog box for editing the a new <code>Person</code> */ void newPerson() { EditPersonDialog dialog = new EditPersonDialog(this.getFrame(), this.tree); dialog.pack(); dialog.setLocationRelativeTo(this); dialog.setVisible(true); Person newPerson = dialog.getPerson(); if (newPerson != null) { // A change was made this.setDirty(true); this.tree.addPerson(newPerson); this.treeList.fillInList(this.tree); } } /** * Brings up a dialog box for noting the current * <code>Person</code>'s marriage. */ void addMarriage() { Person person = this.treeList.getSelectedPerson(); EditMarriageDialog dialog = new EditMarriageDialog(person, this.getFrame(), this.tree); dialog.pack(); dialog.setLocationRelativeTo(this); dialog.setVisible(true); Marriage newMarriage = dialog.getMarriage(); if (newMarriage != null) { // A change was made and update person panel this.setDirty(true); this.showPerson(person); } } /** * Saves the family tree to a file */ private void save() { if (this.file == null) { saveAs(); return; } // System.out.println("Saving to an XML file"); try { XmlDumper dumper = new XmlDumper(this.file); dumper.dump(this.tree); this.setDirty(false); } catch (IOException ex) { error("Error while saving family tree: " + ex); } } /** * Creates a <code>JFileChooser</code> suitable for dealing with * text files. */ private JFileChooser getFileChooser() { JFileChooser chooser = new JFileChooser(); if (this.file == null) { String cwd = System.getProperty("user.dir"); chooser.setCurrentDirectory(new File(cwd)); } else { chooser.setCurrentDirectory(this.file.getParentFile()); } chooser.setFileFilter(new javax.swing.filechooser.FileFilter() { public boolean accept(File file) { if (file.isDirectory()) { return true; } String fileName = file.getName(); return fileName.endsWith(".xml"); } public String getDescription() { return "XML files (*.xml)"; } }); chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); chooser.setMultiSelectionEnabled(false); return chooser; } /** * Displays a dialog box that allows the user to select an XML file * to save the family tree to. */ private void saveAs() { // System.out.println("Saving as..."); JFileChooser chooser = this.getFileChooser(); chooser.setDialogTitle("Save As..."); chooser.setDialogType(JFileChooser.SAVE_DIALOG); int response = chooser.showSaveDialog(this); if (response == JFileChooser.APPROVE_OPTION) { this.file = chooser.getSelectedFile(); if (this.file.exists()) { response = JOptionPane.showConfirmDialog(this, new String[] { "File " + this.file + " already exists.", "Do you want to overwrite it?"}, "Overwrite file?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (response == JOptionPane.NO_OPTION) { // Try it again... saveAs(); return; // Only save once } } save(); } } /** * Pops open a dialog box for choosing an XML file */ private void open() { // System.out.println("Opening an XML file"); if (this.isDirty) { int response = JOptionPane.showConfirmDialog(this, new String[] { "You have made changes to your family tree.", "Do you want to save them?"}, "Confirm changes", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); if (response == JOptionPane.YES_OPTION) { save(); } else if (response == JOptionPane.CANCEL_OPTION) { // Don't open new file, keep working with old return; } // Otherwise, discard changes and open new file } JFileChooser chooser = this.getFileChooser(); chooser.setDialogTitle("Open text file"); chooser.setDialogType(JFileChooser.OPEN_DIALOG); int response = chooser.showOpenDialog(this); if (response == JFileChooser.APPROVE_OPTION) { File file = chooser.getSelectedFile(); FamilyTree tree = null; try { XmlParser parser = new XmlParser(file); tree = parser.parse(); } catch (FileNotFoundException ex) { error(ex.toString()); } catch (FamilyTreeException ex) { error(ex.toString()); } if (tree != null) { // Everything is okay this.file = file; this.sourceLocation.setText(this.file.getName()); this.tree = tree; this.setDirty(false); this.treeList.fillInList(this.tree); this.sourceLocation.setText(this.file.getPath()); } } } /** * Called when the family tree changes dirtiness */ void setDirty(boolean isDirty) { this.isDirty = isDirty; this.saveAsItem.setEnabled(isDirty); this.saveItem.setEnabled(isDirty); } /** * Returns the <code>JFrame</code> associated with this GUI. */ JFrame getFrame() { return this.frame; } /** * If the family tree has been modified and not saved, prompt the * user to verify that he really wants to exit. */ private void exit() { if (this.isDirty) { int response = JOptionPane.showConfirmDialog(this, new String[] { "You have made changes to your family tree.", "Do you want to save them?"}, "Confirm changes", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); if (response == JOptionPane.YES_OPTION) { save(); System.exit(0); } else if (response == JOptionPane.NO_OPTION) { System.exit(0); } // Otherwise continue working } else { System.exit(0); } } /** * Overridden to pack the containing <code>JFrame</code>. */ public void pack() { this.frame.pack(); } /** * Overridden to set the visibility of the containing * <code>JFrame</code>. */ public void setVisible(boolean isVisible) { this.frame.setVisible(isVisible); } /** * Creates a <code>FamilyTreeGUI</code> */ public static void main(String[] args) { FamilyTreeGUI gui = new FamilyTreeGUI("Family Tree Program"); gui.pack(); gui.setVisible(true); } }