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}