package cake;
import gui.CakeFrame;
import gui.CakeGUI;
import gui.ProgramSettingsView;
import gui.SuggestionFrame;
import gui.Tray;
import java.awt.CardLayout;
import java.awt.Frame;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import javax.help.HelpSet;
import javax.help.JHelp;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.KeyStroke;
import observer.Observer;
import subsystem.CakeCal;
import subsystem.Period;
import subsystem.ProgramSettings;
import sun.audio.AudioPlayer;
import sun.audio.AudioStream;
/**
* <h1>Cake Calendar</h1>
*
* The main class of the Cake Calendar System. This handles all of the GUI elements. When you start the Cake Calendar, this makes a new
* instance of itself, CakeCal, and CakeGUI. When adding a new calendar, a new CakeCal and CakeGUI are associated with each other.
* Note that the GUI part of the application exists <i>both</i> here in Cake <i>and</i> in
* CakeGUI. This is because different aspects of the GUI have to be associated with different
* calendars, due to the multiple calendars feature.
*
* @author Robert Middleton
*/
public class Cake implements WindowListener,ActionListener,Observer,ComponentListener{
private CakeFrame mainWindow;
private CardLayout bigViewChanger;
private JPanel main;
private CakeGUI currentView;
//private JMenu viewCal;
private JMenu calendar;
private ArrayList<CakeGUI> calendars;
private ArrayList<JRadioButtonMenuItem> radioButtons;
private final String GUITitle = "Cake Calendar GUI - ";
private String oldName;
private boolean dispose = false;
private SuggestionFrame suggestionFrame;
private ArrayList<String> invalidNames;
private final int TODAYYEAR = CakeCal.getDate().year;
private final int TODAYMONTH = CakeCal.getDate().month;
private final int TODAYDAY = CakeCal.getDate().day;
private final Period forTray = Period.parse( TODAYYEAR + "." + TODAYMONTH + "." + TODAYDAY + ":" + "00.00" + "-"
+ TODAYYEAR + "." + TODAYMONTH + "." + TODAYDAY + ":" + "23.59");
public Tray todaysEvents;
private Rectangle bounds; //the bounds of the JFrame. Used when the window is iconified/deiconified
//Strings to be used for various menus
public static final String OPEN = "Open";
public static final String NEW = "New";
public static final String MERGE = "Merge";
public static final String SAVE = "Save";
public static final String SAVEAS = "Save As...";
public static final String CLOSE = "Close Calendar";
public static final String EXIT = "Exit";
public static final String SETTINGS = "Calendar Settings";
public static final String PROG_SETTINGS = "Program Settings";
public static final String HELP = "Help";
public static final String ABOUT = "About";
public static final String BUGSUGGEST = "Bug report/Suggestion";
public static final String IMPORTICS = "Import .ics file";
public static final String EXPORT_PDF = "Export calendar as PDF";
/**This is the main method that should be used to run the Cake Calendar.
*
* @param args System arguments. If provided, the calendar will start with the
* specified calendar open when it starts
*
*/
public static void main(String args[]) {
CakeCal cal = new CakeCal(args);
CakeGUI gui = new CakeGUI(cal);
Cake c = new Cake( gui );
gui.setParent(c);
ProgramSettings.loadSettings();
}
/**Construct
*
* @param g - The CakeGUI to originally start the program with.
*/
public Cake(CakeGUI g){
currentView = g;
bigViewChanger = new CardLayout();
main = new JPanel(bigViewChanger);
//System.out.println(currentView.getSettings().getName());
main.add(currentView.getPanel(),currentView.getSettings().getName());
//Vlad's Menu
JMenuBar menuBar = new JMenuBar();
//Handling ---> FILE menu
JMenu file = new JMenu("File");
file.setMnemonic(KeyEvent.VK_F);
//NEW
JMenuItem newCalendar = new JMenuItem(NEW);
newCalendar.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_N, ActionEvent.CTRL_MASK));
newCalendar.addActionListener(this);
file.add(newCalendar);
//OPEN
JMenuItem open = new JMenuItem(OPEN);
open.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_O, ActionEvent.CTRL_MASK));
open.addActionListener(this);
file.add(open);
//MERGE
JMenuItem merge = new JMenuItem(MERGE);
merge.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_M, ActionEvent.ALT_MASK));
merge.addActionListener(this);
file.add(merge);
//Import
JMenuItem icsImport = new JMenuItem(IMPORTICS);
icsImport.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_I, ActionEvent.CTRL_MASK));
icsImport.addActionListener(this);
file.add(icsImport);
//Export as PDF
JMenuItem pdfExport = new JMenuItem(EXPORT_PDF);
pdfExport.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_E, ActionEvent.CTRL_MASK));
pdfExport.addActionListener(this);
file.add(pdfExport);
//------------Separator-------------//
file.addSeparator();
//SAVE
JMenuItem save = new JMenuItem(SAVE);
save.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_S, ActionEvent.CTRL_MASK));
save.addActionListener(this);
file.add(save);
//SAVE AS
JMenuItem saveAs = new JMenuItem(SAVEAS);
saveAs.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_S, ActionEvent.ALT_MASK | ActionEvent.CTRL_MASK));
saveAs.addActionListener(this);
file.add(saveAs);
//-----------Separator-------------//
file.addSeparator();
//CLOSE CALENDAR
JMenuItem closeCalendar = new JMenuItem(CLOSE);
closeCalendar.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_C, ActionEvent.ALT_MASK));
closeCalendar.addActionListener(this);
file.add(closeCalendar);
//EXIT
JMenuItem exit = new JMenuItem(EXIT, KeyEvent.VK_X);
exit.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_X, ActionEvent.ALT_MASK));
exit.addActionListener(this);
file.add(exit);
//Handling ---> EDIT menu
JMenu edit = new JMenu("Edit");
edit.setMnemonic(KeyEvent.VK_E);
//SETTINGS
JMenuItem settings = new JMenuItem(SETTINGS);
settings.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_S, ActionEvent.SHIFT_MASK | ActionEvent.CTRL_MASK));
settings.addActionListener(this);
edit.add(settings);
JMenuItem progSettings = new JMenuItem(PROG_SETTINGS);
progSettings.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_P, ActionEvent.SHIFT_MASK | ActionEvent.CTRL_MASK));
progSettings.addActionListener(this);
edit.add(progSettings);
//Handling ---> CALENDAR menu
calendar = new JMenu("Calendar");
calendar.setMnemonic(KeyEvent.VK_C);
JRadioButtonMenuItem cal1 = new JRadioButtonMenuItem(currentView.getSettings().getName(), true);
radioButtons = new ArrayList<JRadioButtonMenuItem>();
radioButtons.add( cal1 );
cal1.addActionListener(this);
calendar.add(cal1);
//Handling ---> HELP menu
JMenu help = new JMenu("Help");
help.setMnemonic(KeyEvent.VK_H);
//HELP
JMenuItem helpItem = new JMenuItem(HELP);
helpItem.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_F1, 0));
helpItem.addActionListener(this);
help.add(helpItem);
JMenuItem aboutItem = new JMenuItem(ABOUT);
aboutItem.addActionListener(this);
help.add(aboutItem);
//bug/suggestion menu
JMenuItem bugItem = new JMenuItem(BUGSUGGEST);
bugItem.addActionListener(this);
help.add(bugItem);
menuBar.add(file);
menuBar.add(edit);
menuBar.add(calendar);
menuBar.add(help);
todaysEvents = new Tray( g.getCakeCal().getEvents(forTray),g.getCakeCal().getSettings().getFilename(),this );
mainWindow = new CakeFrame("CakeGUI - " + currentView.getSettings().getFilename(),this);
mainWindow.add( main );
mainWindow.setJMenuBar(menuBar);
mainWindow.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
mainWindow.addWindowListener(this);
mainWindow.addComponentListener(this);
mainWindow.pack();
mainWindow.setExtendedState(mainWindow.getExtendedState() | Frame.MAXIMIZED_BOTH);
mainWindow.setFocusable(true);
mainWindow.setVisible(true);
//switchComponent( currentView );
bigViewChanger.show(main,currentView.getSettings().getName());
mainWindow.setTitle(GUITitle + currentView.getSettings().getName());
mainWindow.repaint();
calendars = new ArrayList<CakeGUI>();
calendars.add(currentView);
invalidNames = new ArrayList<String>();
invalidNames.add(OPEN);
invalidNames.add(NEW);
invalidNames.add(MERGE);
invalidNames.add(SAVE);
invalidNames.add(SAVEAS);
invalidNames.add(CLOSE);
invalidNames.add(EXIT);
invalidNames.add(SETTINGS);
invalidNames.add(PROG_SETTINGS);
invalidNames.add(HELP);
invalidNames.add(ABOUT);
invalidNames.add(IMPORTICS);
invalidNames.add(EXPORT_PDF);
invalidNames.add(currentView.getSettings().getName());
suggestionFrame = new SuggestionFrame();
}
/**Add a new card with no pre-set GUI. Creates a new instance of CakeGUI and CakeCal.
*
* @return true if succesful, false otherwise
*/
private boolean addNewComp(){
currentView = new CakeGUI(new CakeCal()); //make a new current view
currentView.setParent(this);
//the card layout switches based on the title of the calendar;
//10 calendars named 'New Calendar' don't really accomplish anything
Object name = JOptionPane.showInputDialog(mainWindow, "Enter a title:");
if( name == null){
return false;
}
//check to see if the name is valid
if( invalidNames.contains((String)name) ){
JOptionPane.showMessageDialog(mainWindow, "Sorry, that name is not valid.");
return false;
}
currentView.getSettings().setName((String)name);
//makes none of the radio buttons selected.
for( int x = 0; x < radioButtons.size(); x++){
radioButtons.get(x).setSelected(false);
}
//add another radio button and an action listener, make it selected
JRadioButtonMenuItem cal1 = new JRadioButtonMenuItem(currentView.getSettings().getName(),true);
cal1.addActionListener(this);
calendar.add(cal1);
radioButtons.add( cal1 );
//add the new pane to the card layout, update the title
main.add(currentView.getPanel(), currentView.getSettings().getName());
mainWindow.setTitle(GUITitle + currentView.getSettings().getName());
mainWindow.repaint();
calendars.add(currentView);
switchComponent(currentView);
return true;
}
/**Switch to the specified component.
*
* @param card
*/
private void switchComponent( CakeGUI card ){
//currentView = card;
bigViewChanger.show(main,card.getSettings().getName());
//makes none of the radio buttons selected.
for( int x = 0; x < radioButtons.size(); x++){
radioButtons.get(x).setSelected(false);
if( radioButtons.get(x).getText().equals(card.getSettings().getName())){
radioButtons.get(x).setSelected(true);
}
}
mainWindow.setTitle(GUITitle + currentView.getSettings().getName());
}
/**Called when the window is activated. Ignored.
*
* @param e The window event to do something with.
*/
public void windowActivated(WindowEvent e) {
}
/**Called when the window is closed.
*
* @param e The WindowEvent to do something with
*/
public void windowClosed(WindowEvent e) {
}
/**Called when the window is closing, and when the user goes to File -> Exit
*
* @param e The WindowEvent to do something with.
*/
public void windowClosing(WindowEvent e) {
for( int x = 0; x < calendars.size(); x++){
currentView = calendars.get(x);
int selection = 3; //non-existant selection by default
if( currentView.isModified() ){
selection = JOptionPane.showConfirmDialog(mainWindow,"Save calendar " + currentView.getSettings().getName() + " before Quitting?");
//System.out.println(selection);
if( selection == 0 && !currentView.isModified()){ // 0 = yes
currentView.saveFile();
}
else if( selection == 0 ){
currentView.saveFileAs();
}
}
if( selection == 2 ){
dispose = false;
//return;
}
else{
dispose = true;
}
}
mainWindow.dispose();
//System.exit(0);
}
/**Called when the window is deactivated. Ignored.
*
*/
public void windowDeactivated(WindowEvent e) {
}
/**Called when the window is Deiconified.
*
*/
public void windowDeiconified(WindowEvent e) {
if(ProgramSettings.getVars(ProgramSettings.minimizeToTray).getBoolean() == true){
mainWindow.setVisible(true);
mainWindow.setBounds(bounds);
mainWindow.toFront();
}
}
/**Called when the window is iconified. Checks to see if we should minimize to tray,
* and if we should set the window to be invisible.
*
*/
public void windowIconified(WindowEvent e) {
if(ProgramSettings.getVars(ProgramSettings.minimizeToTray).getBoolean() == true){
bounds = mainWindow.getBounds();
mainWindow.setVisible(false);
}
}
/**Called when the window is opened. Ignored.
*
*/
public void windowOpened(WindowEvent e) {
}
/**Does an action depending on the specified ActionEvent. Handles switching calendars, and menu bar. Other actions are performed
* by the CakeGUI.
*
* @param e The ActionEvent to do something with.
*/
public void actionPerformed(ActionEvent e) {
if( e.getActionCommand().equals(NEW)){
if(!addNewComp()){
return;
}
}
else if( e.getActionCommand().equals(EXIT) ) {
this.windowClosing(new WindowEvent(mainWindow,WindowEvent.WINDOW_CLOSING));
}else if (e.getActionCommand().equals(OPEN)) {
if( currentView.isModified() || currentView.isOpenState() ){
if(!openNewComp()){
return;
}
}else if(currentView.openFile(false) == false){
return;
}
//check to see if the name is valid
if( invalidNames.contains(currentView.getSettings().getName()) ){
JOptionPane.showMessageDialog(mainWindow, "You already have a calendar with that name open!");
bigViewChanger.removeLayoutComponent(currentView.getPanel());
//remove the calendar from the array list of calendars
for( int x = 0; x < calendars.size(); x++){
if( calendars.get(x).equals(currentView)){
calendars.remove(x);
}
}
//remove the calendar from the array list of radio buttons
for( int x = 0; x < radioButtons.size(); x ++){
if( radioButtons.get(x).getText().equals(currentView.getSettings().getName())){
calendar.remove(radioButtons.get(x));
radioButtons.remove(x);
}
}
//update the view(defaults to the first calendar)
currentView = calendars.get(0);
switchComponent( currentView );
radioButtons.get(0).setSelected(true);
return;
}
invalidNames.add(currentView.getSettings().getName() );
//bigViewChanger.addLayoutComponent(currentView.getPanel(), currentView.getSettings().getName());
//switchComponent(currentView);
if(radioButtons.size() == 1)
radioButtons.get(0).setSelected(true);
mainWindow.setTitle( GUITitle + currentView.getSettings().getName() );
updateCals();
} else if (e.getActionCommand().equals(SAVE)) {
if (currentView.getSettings().getName() == CakeCal.UNTITLED) {
currentView.saveFileAs();
} else currentView.saveFile();
} else if (e.getActionCommand().equals(SAVEAS)) {
currentView.saveFileAs();
} else if( e.getActionCommand().equals(SETTINGS)){
oldName = currentView.getSettings().getName();
currentView.showSettings();
}else if( e.getActionCommand().equals(PROG_SETTINGS)){
ProgramSettingsView.showSettings();
} else if( e.getActionCommand().equals(CLOSE)){
int selection = 3; //non-existant selection by default
if( currentView.isModified() ){
selection = JOptionPane.showConfirmDialog(mainWindow,"Close Calendar " + currentView.getSettings().getName() + "?");
//System.out.println(selection);
}
if( selection == JOptionPane.OK_OPTION && !currentView.isModified()){
currentView.saveFile();
}
else if( selection == JOptionPane.OK_OPTION ){
currentView.saveFileAs();
}
if( selection == JOptionPane.CANCEL_OPTION ){
return;
}
bigViewChanger.removeLayoutComponent(currentView.getPanel());
//remove the calendar from the array list of calendars
for( int x = 0; x < calendars.size(); x++){
if( calendars.get(x).equals(currentView)){
calendars.remove(x);
}
}
//remove the calendar from the list of invalid names
for(int x = 0; x < invalidNames.size(); x++){
if(invalidNames.get(x).equals(currentView.getSettings().getName())){
invalidNames.remove(x);
}
}
//remove the calendar from the array list of radio buttons
for( int x = 0; x < radioButtons.size(); x ++){
if( radioButtons.get(x).getText().equals(currentView.getSettings().getName())){
calendar.remove(radioButtons.get(x));
radioButtons.remove(x);
}
}
//update the view(defaults to the first calendar)
if( calendars.size() > 0){
currentView = calendars.get(0);
radioButtons.get(0).setSelected(true);
switchComponent( currentView );
}
else{
JOptionPane.showMessageDialog(mainWindow,"Sorry, but you can't close that","Close Error",0);
}
}else if( e.getActionCommand().equals(HELP)){
JHelp helpViewer = null;
try {
ClassLoader cl = Cake.class.getClassLoader();
URL url = HelpSet.findHelpSet(cl, "doc/helpset.hs");
helpViewer = new JHelp(new HelpSet(cl, url));
helpViewer.setCurrentID("Cake.Introduction");
} catch (Exception x) {
JOptionPane.showMessageDialog(null, "API Help set not found");
}
// Create a new frame.
JFrame frame = new JFrame("Cake Calendar Help");
// Set it's size.
frame.setBounds(new Rectangle((mainWindow.getWidth()/2)-400, (mainWindow.getHeight()/2)-300, 800, 600));
// Add the created helpViewer to it.
frame.getContentPane().add(helpViewer);
// Set a default close operation.
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// Make the frame visible.
frame.setVisible(true);
} else if (e.getActionCommand().equals(ABOUT)) {
try {
InputStream in = new FileInputStream("sounds/easteregg1.wav");
AudioStream as = new AudioStream(in);
AudioPlayer.player.start(as);
AudioPlayer.player.stop(as);
} catch (IOException e1) {
e1.printStackTrace();
}
JOptionPane.showMessageDialog(mainWindow,"\t\t\t\t\t\t\t\t\tCake Calendar\n\n"+
"Code Maintained by: \nRobert Middleton\n"+
"Visit our webpage at \n\t\nhttp://cakecalendar.sourceforge.net\n\n"+
"Please report any bugs to our website.\n\n"+
"\nOriginally Made By:\nHashem Assayari\n" +
"Dana Burkart\nVladimir Hadzhiyski\nRobert Middleton\nJack Zhang" +
"\n\n\n2009");
} else if( e.getActionCommand().equals(MERGE)){
currentView.openFile(true);
} else if( e.getActionCommand().equals(BUGSUGGEST)){
suggestionFrame.clickedOn();
} else if( e.getActionCommand().equals(IMPORTICS)){
currentView.openICS();
}else if( e.getActionCommand().equals(EXPORT_PDF)){
currentView.exportAsPDF();
}
//search thru the array to find the right card to switch to
//if there is no calendar with this name, it does not switch
for( int x = 0; x < calendars.size(); x++ ){
if( calendars.get(x).getSettings().getName().equals(e.getActionCommand())){
currentView = calendars.get(x);
switchComponent( currentView );
}
}
if( currentView.isModified() == true ) {
todaysEvents.updateTodayEvent( currentView.getCakeCal().getEvents(forTray));
}
}
/**Update the GUI to reflect the new changes in the title of a calendar.
*
*/
public void updateGUI(){
bigViewChanger.removeLayoutComponent(currentView.getPanel());
main.add(currentView.getPanel(), currentView.getSettings().getName());
bigViewChanger.show(main, currentView.getSettings().getName());
mainWindow.setTitle(GUITitle + currentView.getSettings().getName());
for( int x = 0; x < radioButtons.size(); x++ ){
if( radioButtons.get(x).getText().equals(oldName)){
radioButtons.get(x).setText(currentView.getSettings().getName());
}
}
}
/**Called by CakeGUI with the new name of the calendar when saving a file. Sets up a proper call to updateGUI()
*
* @param name The old name of the calendar
*/
public void updateGUI( String name ){
oldName = name;
updateGUI();
}
/**Open a new component; brings up an open dialog instead of prompting for a title.
*
*/
private boolean openNewComp(){
if( currentView.getSettings().getName().equals(CakeCal.UNTITLED) && currentView.isModified()){
//the card layout switches based on the title of the calendar;
//10 calendars named 'New Calendar' don't really accomplish anything
Object name = JOptionPane.showInputDialog(mainWindow, "Rename this Calendar:");
if( name == null ){
return false;
}
currentView.getSettings().setName((String)name);
main.add(currentView.getPanel(), currentView.getSettings().getName());
}
currentView = new CakeGUI(new CakeCal()); //make a new current view
currentView.setParent(this);
currentView.openFile(false);
//makes none of the radio buttons selected.
for( int x = 0; x < radioButtons.size(); x++){
radioButtons.get(x).setSelected(false);
}
//add another radio button and an action listener, make it selected
JRadioButtonMenuItem newCalendar = new JRadioButtonMenuItem(currentView.getSettings().getName(),true);
newCalendar.addActionListener(this);
newCalendar.setSelected(true);
calendar.add(newCalendar);
radioButtons.add( newCalendar );
//add the new pane to the card layout, update the title
main.add(currentView.getPanel(), currentView.getSettings().getName());
mainWindow.setTitle(GUITitle + currentView.getSettings().getName());
mainWindow.repaint();
calendars.add(currentView);
switchComponent(currentView);
return true;
}
/**Update the titles of the radio buttons
*
*/
private void updateCals(){
String formerName = currentView.getCakeCal().getSettings().getName();
for( int x = 0; x < calendars.size(); x++ ){
radioButtons.get(x).setText(calendars.get(x).getSettings().getName());
}
if( currentView.getCakeCal().getSettings().getFilename() != formerName ) {
todaysEvents.setName(currentView.getCakeCal().getSettings().getFilename());
todaysEvents.updateTodayEvent( currentView.getCakeCal().getEvents(forTray));
}
}
/**Do we want to kill the process?
*
* @return true if the user wants to exit the program, false otherwise
*/
public boolean dispose(){
return dispose;
}
/**Get new data once a change has been made.
*
*/
public void updateData() {
//we want to update the current CakeGUI that we have open.
currentView.updateData();
}
/**Opens the window after it has been minimized to the tray
*
*/
public void openWindow(){
mainWindow.toFront();
this.windowDeiconified(new WindowEvent(mainWindow, 0));
}
public void componentHidden(ComponentEvent arg0) {
}
public void componentMoved(ComponentEvent arg0) {
}
public void componentResized(ComponentEvent arg0) {
currentView.updateSize(mainWindow.getSize());
}
public void componentShown(ComponentEvent arg0) {
}
}