#!/usr/bin/perl
# Day planner
# A graphical day planner written in perl that uses Gtk2
# Copyright (C) Eskild Hustvedt 2006
# $Id: dayplanner 500 2006-08-02 16:10:20Z zero_dogg $
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

use strict;			# Force strict coding
use warnings;			# Tell perl to warn about things
use Locale::gettext;		# Allow the program to be translated
use POSIX;			# We need strftime
use Gtk2; 			# Use Gtk2 :)
use Data::Dumper;		# Our save format is that of Data::Dumper
use Gtk2::SimpleList;		# We use Gtk2::SimpleList to create the eventlist
use Gtk2::Gdk::Keysyms;		# Easier keybindings
use Getopt::Long;		# Commandline options
use Cwd;			# We need Cwd::Realpath to find out which directory we live in
use File::Basename;             # We meed dirname to help Cwd::Realpath finding the directory
use IO::Socket;			# Network layer to communicate with the daemon
use File::Copy;			# We need to copy the holiday file
use FindBin;			# So that we can detect module dirs during runtime
# So that we can use a local Date::HolidayParser
use lib "$FindBin::RealBin/modules/Date-HolidayParser/lib/";
use lib "$FindBin::RealBin/modules/";
use Date::HolidayParser;	# Parsing of .holiday files

# Scalars
my $Version = "0.2";
my $RCSRev = '$Id: dayplanner 500 2006-08-02 16:10:20Z zero_dogg $';
my $MainEventsFile = "events.dpd";			# The filename to save the events to
my $SaveToDir;						# The configuration and eventlist directory (set later)
my $HolidayFile;					# The file to load holiday definitions from (set later)
my $BirthdayFile = "birthdays.dpd";			# The filename to save birthdays to
my $SpecialEventsFile = "special_events.dpd";		# The filename to save events with special settings
my $AllDayEventsFile = "allday_events.dpd";		# The filename to save all-day events to
my $ConfigFile = "dayplanner.conf";			# The filename to save the configuration to
my $Gettext = Locale::gettext->domain("dayplanner");	# Set the gettext domain
my $DaemonName = 'dayplanner-daemon';			# The name of the daemon
my $DaemonOnline = 0;					# Is the daemon online?
my $DaemonInitialized = 0;				# Has the daemon been initialized?
my $DaemonSocketName = 'dayplannerd';			# The name of the daemon socket
my $DaemonSocket;					# The variable to connect to

setlocale(LC_ALL, "" );
textdomain("dayplanner");

# Find out if we have a locale directory in our main dir
if (-d "$FindBin::RealBin/locale/") {
	if(defined($ENV{LC_ALL}) or defined($ENV{LANG})) {
		my $I18N;
		if(defined($ENV{LC_ALL}) and length($ENV{LC_ALL})) {
			$I18N = $ENV{LC_ALL};
		} else {
			$I18N = $ENV{LANG};
		}
		if($I18N =~ /:/) {
			$I18N =~ s/^(.+):.*$/$1/;
		}
		if (-e "$FindBin::RealBin/locale/$I18N/LC_MESSAGES/dayplanner.mo") {
			bindtextdomain("dayplanner", "$FindBin::RealBin/locale");
		}
	}
}

# Arrays
my @SaveFallbackDirs = (				# The directories to use for fallback saving if we can't save to $SaveToDir
	$ENV{HOME}, "$ENV{HOME}/Desktop", "$ENV{HOME}/Documents", "$ENV{HOME}/tmp", "/tmp", "/var/tmp", "/usr/tmp"
);

# Hashes
my %CalendarContents;		# The contents of the main calendar
my %BirthdayContents;		# The contents of the birthday file
my %SpecialEvents;		# The contents of the special events file
my %AllDayEvents;		# The contents of the all-day events file
my %Holidays;			# The holidays
my %InternalConfig;		# Internal configuration values
my %UserConfig;			# User-selected configuration values

# The reason we do not use strftime() or I18N::Langinfo is that these return
# values in random encodings. By using Gettext->get() we get values
# in a proper encoding
my %MonthNames = (
	1 => $Gettext->get("January"),
	2 => $Gettext->get("February"),
	3 => $Gettext->get("March"),
	4 => $Gettext->get("April"),
	5 => $Gettext->get("May"),
	6 => $Gettext->get("June"),
	7 => $Gettext->get("July"),
	8 => $Gettext->get("August"),
	9 => $Gettext->get("September"),
	10 => $Gettext->get("October"),
	11 => $Gettext->get("November"),
	12 => $Gettext->get("December")
);	# Localized hash of month number => Name values

# Gtk2 objects
my (
	$CalendarWidget,	$EventlistWidget,	$WorkingAreaHBox,
	$EventlistWin,		$MainWindow,		$Toolbar,
	$ToolbarEditButton,	$MenuEditEntry,		$MenuDeleteEntry,
	$UpcomingEventsBuffer,	$UpcomingEventsWidget,
);	# Gtk objects

my $HolidayParser; # The Date::HolidayParser object

# Global date variables
my (
	$currsec,$currmin,$currhour,$currmday,$currmonth,$curryear,$currwday,$curryday,$currisdst
);

# Configuration options
#my (
#	$UserConfig{EditorVerboseDefault},	$UserConfig{Events_NotifyPre},
#	$UserConfig{Events_NotifyDay},	$UserConfig{Events_DayNotify}
#);

# Window state
($InternalConfig{MainWin_Width}, $InternalConfig{MainWin_Height}) = (600,365);	# Default size is 600x365 - overridden by state.conf

# Set up signal handlers
$SIG{INT} = \&DP_SigHandler;
$SIG{TERM} = \&DP_SigHandler;


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Networking functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Initialize and connect to the daemon
# Usage: DaemonInit();
sub DaemonInit {
	$DaemonInitialized = 1;
	
	# Handle SIGPIPE
	$SIG{PIPE} = \&DaemonSIGPIPEHandler;

	if (-e "$SaveToDir/$DaemonSocketName") {
		my $ConnectionAttempt = ConnectToDaemon("$SaveToDir/$DaemonSocketName");
		if ($ConnectionAttempt eq 'NOHI') {
			DaemonConnectionErrorHandler();
			return(1);
		} elsif ($ConnectionAttempt eq '1') {
			$DaemonOnline = 1;
			return(1);
		} 
	}
	return(StartDaemon());
}

# Purpose: Start the day planner daemon
# Usage: StartDaemon();
sub StartDaemon {
	foreach(split(/:/, sprintf("%s:%s", dirname(Cwd::realpath($0)), $ENV{PATH} ))) {
		if (-x "$_/$DaemonName") {
			# Yes this is a bit weird statement. But remember that commands return 0 on TRUE
			# and 1-255 on FALSE, so it's the other way around from perl
			unless(system("$_/$DaemonName --force-fork --dayplannerdir $SaveToDir")) {
				if(ConnectToDaemon("$SaveToDir/$DaemonSocketName")) {
					$DaemonOnline = 1;
					return(1);
				}
			}
		}
	}
	DPIntWarn("Unable to start daemon!");
	return(0);
}

# Purpose: Handle SIGPIPE gracefully
# Usage: $SIG{PIPE} = \&DaemonSIGPIPEHandler;
sub DaemonSIGPIPEHandler {
	DPIntWarn("I recieved a SIGPIPE, the daemon is probably offline");
	StartDaemon();
}

# Purpose: Connect to the daemon
# Usage: ConnectToDaemon(/path/to/socket, FORCE/undef);
sub ConnectToDaemon {
	my $HI = 'HI client';
	if(defined($_[1]) and $_[1] eq 'FORCE') {
		$HI = 'HI client FORCE';
	}
	$DaemonSocket = IO::Socket::UNIX->new(
		Peer	=> $_[0],
		Type	=> SOCK_STREAM,
		Timeout	=> 5)
		or return("NOCONNECT");
	$DaemonOnline = 1;	# If DaemonOnline isn't 1 then Daemon_DataSegment won't send the data
	if(Daemon_DataSegment($HI) eq "HI\n") {
		return(1);
	} else {
		$DaemonOnline = 0;
		close($DaemonSocket);
		return("NOHI");
	}
}

# Purpose: Send some data to the daemon
# Usage: Daemon_SendData(DATA);
sub Daemon_SendData {
	if($DaemonOnline) {
		unless(defined($DaemonSocket)) {
			DPIntWarn("\$DaemonSocket is suddenly undef, attempting reconnect");
			Daemon_AttemptReconnect() or DPIntWarn("Unable to reconnect, expect trouble") and return(0);
		}
		if(print $DaemonSocket "$$ $_[0]","\n") {
			return(1);
		} else {
			DPIntWarn("Problem sending data to the daemon: $!");
		}
	} 
	return(0);
}

# Purpose: Reconnect after $DaemonSocket for some reason has gone undef
# Usage: Daemon_AttemptReconnect();
sub Daemon_AttemptReconnect {
	if(ConnectToDaemon("$SaveToDir/$DaemonSocketName", undef) eq 'NOHI') {
		return(undef);
	} else {
		return(1);
	}
}

# Purpose: Get some data from the daemon
# Usage: $Data = Daemon_GetData();
sub Daemon_GetData {
	if($DaemonOnline) {
		unless(defined($DaemonSocket)) {
			DPIntWarn("\$DaemonSocket is suddenly undef, attempting reconnect");
			Daemon_AttemptReconnect() or DPIntWarn("Unable to reconnect, expect trouble") and return("ERR NOT_ONLINE");
		}
		my $Data = <$DaemonSocket>;
		unless(defined($Data)) {
			return("ERR UNABLE_TO_GET_DATA");
		}
		return($Data);
	} else {
		return("ERR NOT_ONLINE");
	}
}

# Purpose: Send something to the daemon and then return the daemons reply
# Usage: $Reply = Daemon_DataSegment(DATA);
sub Daemon_DataSegment {
	if($DaemonOnline) {
		if(Daemon_SendData($_[0])) {
			return(Daemon_ErrorHandler(Daemon_GetData()));
		}
	} else {
		return("ERR NOT_ONLINE");
	}
}

# Purpose: Close the connection to the daemon
# Usage: CloseDaemon();
sub CloseDaemon {
	if ($DaemonOnline) {
		unless(defined($DaemonSocket)) {
			DPIntWarn("\$DaemonSocket is suddenly undef. Doesn't matter though since we're closing the connection anyway.");
		} else {
			my $GetDaemon = Daemon_DataSegment('BYE');
			close($DaemonSocket);
		}
		$DaemonOnline = 0;
	}
	return(1);
}

# Purpose: Display an error message when an error is recieved from the daemon
# Usage: Daemon_ErrorHandler(Daemon_GetData());
sub Daemon_ErrorHandler {
	if ($_[0] =~ /^ERR\s+/) {
		DPIntWarn("Recived error from daemon: \"$_[0]\"");
	}
	if(defined($_[1])) {
		DPIntWarn("Weird multi-return response from caller: [1]=\"$_[1]\"");
	}
	return($_[0]);
}

# Purpose: Display an error dialog about the inability to connect to the daemon
# Usage: DaemonConnectionErrorHandler();
sub DaemonConnectionErrorHandler {
	my $ErrorDialog = Gtk2::MessageDialog->new (undef,
						'destroy-with-parent',
						'error',
						'none',
						 $Gettext->get("Another instance of day planner appears to be running. Please quit the currently running instance of day planner before continuing\n\nRunning two instances of day planner can be forced. However that is not recommended as it may lead to data loss or corruption."));

	$ErrorDialog->add_buttons(
		$Gettext->get('_Force') => 'reject',
		'gtk-ok' => 'accept',
	);
	
	$ErrorDialog->set_default_response('accept');
	if($ErrorDialog->run eq 'accept') {
		$ErrorDialog->destroy;
		exit(0)
	} else {
		$ErrorDialog->destroy;
		unless (ConnectToDaemon("$SaveToDir/$DaemonSocketName", 'FORCE')) {
				DPError($Gettext->get("Unable to force the start. This is likely due to a bug in the program, please contact the day planner developers."));
				exit(1);
			}
	}
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Data and configuration file functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Write a configuration file
# Usage: WriteConfigFile(/FILE, \%ConfigHash, \%ExplanationHash);
sub WriteConfigFile {
	my ($File, $Config, $Explanations) = @_;

	# Open the config for writing
	open(my $CONFIG, ">", "$File") or do {
		# If we can't then we error out, no need for failsafe stuff - it's just the config file
		DPIntWarn("Unable to save the configuration file $File: $!");
		return(0);
	};
	if(defined($Explanations->{HEADER})) {
		print $CONFIG "# $Explanations->{HEADER}\n";
	}
	foreach(sort(keys(%{$Config}))) {
		next unless length($Config->{$_});	# Don't write empty options
		if(defined($Explanations->{$_})) {
			print $CONFIG "\n# $Explanations->{$_}";
		}
		print $CONFIG "\n$_=$Config->{$_}\n";
	}
	close($CONFIG);
}

# Purpose: Load a configuration file
# Usage: LoadConfigFile(/FILE, \%ConfigHash, \%OptionRegexHash, OnlyValidOptions?);
#  OptionRegeXhash can be available for only a select few of the config options
#  or skipped completely (by replacing it by undef).
#  If OnlyValidOptions is true it will cause LoadConfigFile to skip options not in
#  the OptionRegexHash.
sub LoadConfigFile {
	my ($File, $ConfigHash, $OptionRegex, $OnlyValidOptions) = @_;

	open(my $CONFIG, "<", "$File") or do {
		DPError($Gettext->get(sprintf("Unable to read the configuration settings from %s: %s", $File, $!)));
		return(0);
	};
	while(<$CONFIG>) {
		next if m/^\s*(#.*)?$/;
		next unless m/=/;
		chomp;
		my $Option = $_;
		my $Value = $_;
		$Option =~ s/^\s*(.*)\s*=.*/$1/;
		$Value =~ s/^.*=\s*(.*)\s*/$1/;
		if($OnlyValidOptions) {
			unless(defined($OptionRegex->{$Option})) {
				DPIntWarn("Unknown configuration option \"$Option\" in $File: Ignored.");
				next;
			}
		}
		unless(defined($Value)) {
			DPIntWarn("Empty value for option $Option in $File");
		}
		if(defined($OptionRegex) and defined($OptionRegex->{$Option})) {
			my $MustMatch = $OptionRegex->{$Option};
			unless ($Value =~ /$MustMatch/) {
				print "Invalid setting of $Option in the config file: Must match $OptionRegex->{Option}.\n";
				next;
			}
		}
		$ConfigHash->{$Option} = $Value;
	}
	close($CONFIG);
}

# Purpose: Write the state file 
# Usage: WriteStateFile(DIRECTORY, FILENAME);
sub WriteStateFile {
	# The parameters
	my $Dir = $_[0];
	my $File = $_[1];

	my $NewWinState = 1;

	my %Explenations = (
		MainWin_Maximized => "If the main window is maximized or not (0=false, 1=true)",
		MainWin_Width => "The width of the main window",
		MainWin_Height => "The height of the main window",
		AutostartOn => "If the preferences setting for autostarting the daemon on login is on or not",
		AddedAutostart => "If day planner has (automatically) set the daemon to start on login (this ONLY sets if it has been automatically added or not. It doesn't say anything about IF it is currently added. AutostartOn sets that.)",
		Holiday_Attempted => "The last day planner version the holiday file was attempted set up (but failed)",
		Holiday_Setup => "If the holiday file has been properly set up or not (ie. not a dummy file)",
		HEADER => "This file contains internal configuration used by day planner\n# In most cases you really don't want to edit this file manually",
	);

	
	if(defined($InternalConfig{MainWin_Maximized}) and $InternalConfig{MainWin_Maximized} =~ /maximized/) {
		$InternalConfig{MainWin_Maximized} = 1;
	} else {
		$InternalConfig{MainWin_Maximized} = 0;
		if($NewWinState) {
			($InternalConfig{MainWin_Width}, $InternalConfig{MainWin_Height}) = $MainWindow->get_size();
		}
	}
	
	# Write the actual file
	WriteConfigFile("$Dir/$File", \%InternalConfig, \%Explenations);
}

# Purpose: Load the state file
# Usage: LoadStateFile(DIRECTORY, FILENAME);
sub LoadStateFile {
	# The parameters
	my $Dir = $_[0];
	my $File = $_[1];
	if(-e "$Dir/$File") {
		LoadConfigFile("$Dir/$File", \%InternalConfig, undef, 0);
	}
}

# Purpose: Write the configuration file
# Usage: WriteConfig(DIRECTORY, FILENAME);
sub WriteConfig {
	# The parameters
	my $Dir = $_[0];
	my $File = $_[1];
	# Verify the options first
	unless(defined($UserConfig{EditorVerboseDefault}) and length($UserConfig{EditorVerboseDefault})) {
		$UserConfig{EditorVerboseDefault} = 0;
	}
	unless(defined($UserConfig{Events_NotifyPre}) and length($UserConfig{Events_NotifyPre})) {
		$UserConfig{Events_NotifyPre} = "30min";
	}
	unless(defined($UserConfig{Events_DayNotify}) and length($UserConfig{Events_DayNotify})) {
		$UserConfig{Events_DayNotify} = 0;
	}
	my %Explanations = (
		EditorVerboseDefault => "If the EventEditor should expand the \"details\" by default\n#  1 = expand\n#  0 = don't expand",
		Events_NotifyPre => "If day planner should notify about an event ahead of time.\n#  0 = Don't notify\n# Other valid values: 10min, 20min, 30min, 45min, 1hr, 2hrs, 4hrs, 6hrs",
		Events_DayNotify => "If day planner should notify about an event one day before it occurs.\n#  0 - Don't notify one day in advance\n#  1 - Do notify one day in advance",
		HEADER => "Day planner $Version configuration file",
	);
	
	# Write the actual file
	WriteConfigFile("$Dir/$File", \%UserConfig, \%Explanations);

	# Tell the daemon to reload the config file
	if($DaemonInitialized) {
		unless(Daemon_DataSegment("RELOAD_CONFIG") eq "done\n") {
			# TODO: GUI dialog?
			DPIntWarn("Failed to tell the daemon to reload its configuration file. This might cause problems");
		}
	}
}

# Purpose: Load the configuration file
# Usage: LoadConfig(DIRECTORY, FILENAME);
sub LoadConfig {
	# The parameters
	my $Dir = $_[0];
	my $File = $_[1];
	# If it doesn't exist then we just let WriteConfig handle it
	unless (-e "$Dir/$File") {
		WriteConfig($Dir, $File);
		return(1);
	}
	
	my %OptionRegexHash = (
			EditorVerboseDefault => '^\d+$',
			Events_NotifyPre => '^(\d+(min|hrs?){1}|0){1}$',
			Events_DayNotify => '^\d+$',
		);

	LoadConfigFile("$Dir/$File", \%UserConfig, \%OptionRegexHash, undef);
	return(1);
}

# Purpose: Load the default configuration file
# Usage: LoadDefaultConfig();
sub LoadDefaultConfig {
	CreateSaveDir();
	return(LoadConfig($SaveToDir, $ConfigFile));
}

# Purpose: Save the default configuration file
# Usage: SaveDefaultConfig();
sub SaveDefaultConfig {
	return(WriteConfig($SaveToDir, $ConfigFile));
}

# Purpose: Create the directory in $SaveToDir if it doesn't exist and display a error if it fails
# Usage: CreateSaveDir();
sub CreateSaveDir {
	unless (-e $SaveToDir) {
		mkdir($SaveToDir) or do {
				DPError($Gettext->get(sprintf("Unable to create the directory %s: %s\nManually create this directory before closing this dialog.", $SaveToDir, $!)));
				unless(-d $SaveToDir) {
					die("$SaveToDir does not exist, I was unable to create it and the user didn't create it\n");
				}
			}
	}
}

# Purpose: Save the datafile
# Usage: SaveDatafile(DIRECTORY, FILENAME, HashRef to save, Name of the hash);
sub SaveDatafile {
	my $SAVEFILE;
	# Create the progress window
	my $ProgressWin = Gtk2::Window->new();
	$ProgressWin->set_modal(1);
	$ProgressWin->set_transient_for($MainWindow);
	$ProgressWin->set_position('center-on-parent');
	$ProgressWin->set_title($Gettext->get("Saving..."));
	$ProgressWin->set_resizable(0);
	# Create the progress bar
	my $ProgressBar = Gtk2::ProgressBar->new();
	$ProgressBar->{activity_mode} = 0;
	$ProgressBar->set_fraction(0.0);
	# Add the bar to the window and show them
	$ProgressWin->add($ProgressBar);
	$ProgressBar->show();
	$ProgressWin->show();
	# We want the dumper to be pure (aka. make dumper output a proper syntax that is suitable for eval() or do())
	$Data::Dumper::Purity = 1;
	# Sort the keys
	$Data::Dumper::Sortkeys = 1;
	# Set the indentation
	$Data::Dumper::Indent = 1;
	# Pulse once
	$ProgressBar->pulse();
	# Create the save directory
	CreateSaveDir();
	# Pulse once
	$ProgressBar->pulse();
	# Open the savefile ($_[0]/$_[1]) for writing
	open($SAVEFILE, ">", "$_[0]/$_[1]") or do {
		# EMERGENCY FALLBACK
		
		# Okay, we couldn't open it. This is bad.
		# Now we try to detect a directory to fall back to.
		my $FallbackDirectory;
		my $Error = $!;
		my $FallbackFailed = 1;
		# Try to find a directory we can use
		foreach my $Fallback (@SaveFallbackDirs) {
			$ProgressBar->pulse();
			if (-e $Fallback and -w $Fallback) {
				if (-e "$Fallback/$_[1]" and ! -w "$Fallback/$_[1]") {
					next;
				}
				$FallbackDirectory = $Fallback;
				last;
			}
		}
		# Hide the progress window
		$ProgressWin->hide();
		# If $FallbackDirectory is true then display the error and try to open the fallback
		if ($FallbackDirectory) {
			DPError(sprintf($Gettext->get("FATAL ERROR: Unable to open %s for writing: %s\nWriting to %s instead"), "$_[0]/$_[1]", $Error, "$FallbackDirectory/$_[1]"));
			$FallbackFailed = 0;
			open($SAVEFILE, ">", "$FallbackDirectory/$_[1]") or $FallbackFailed = 1;
		} 
		# If FallbackDirectory failed or we didn't find one - output to STDOUT
		if ($FallbackFailed) {
			if ($FallbackDirectory) {	# If $FallbackDirectory was attempted but failed then display this
				DPError(sprintf($Gettext->get("FATAL ERROR: Unable to open %s for writing: %s\nWriting to %s instead"), "$FallbackDirectory/$_[1]", $Error, "STDOUT"));
			} else {			# If $FallbackDirectory wasn't possible, display this
				DPError(sprintf($Gettext->get("FATAL ERROR: Unable to open %s for writing: %s\nWriting to %s instead"), "$_[0]/$_[1]", $Error, "$FallbackDirectory/$_[1]"));
			}
			print STDOUT Dumper(\%CalendarContents);
			# By returning 0 we tell the caller that something went very wrong and that we
			# might not want to quit if that's what we're doing.
			return(0);
		}
		# Show the progress window again
		$ProgressWin->show();
	};
	# Print this
	print $SAVEFILE "# Day Planner data file for day planner version $Version\n";
	print $SAVEFILE "# Last saved on: ", scalar(localtime),"\n";
	print $SAVEFILE "#\n# The format is explained in doc/SaveformatSyntax\n";
	# Pulse once
	$ProgressBar->pulse();
	# Dump the data
	print $SAVEFILE Data::Dumper->Dump([$_[2]], ["*$_[3]"]);
	# Pulse once and close
	$ProgressBar->pulse();
	close($SAVEFILE);
	# Destroy the progress bar window
	$ProgressWin->destroy();
	# We successfully saved the data
	return(1);
}

# Purpose: Save the main data files
# Usage: SaveMainData();
sub SaveMainData {
	my $Return = 'OKAY';
	unless(SaveDatafile($SaveToDir, $MainEventsFile, \%CalendarContents, "CalendarContents")) {
		$Return	= 'SAVE_FAILED';
	}
	unless(SaveDatafile($SaveToDir, $SpecialEventsFile, \%SpecialEvents, "SpecialEvents")) {
		$Return = 'SAVE_FAILED';
	}
	unless(SaveDatafile($SaveToDir, $BirthdayFile, \%BirthdayContents, "BirthdayContents")) {
		$Return = 'SAVE_FAILED';
	}
	# This to avoid unneccesary overhead in the daemon reloading files needlessly
	unless(Daemon_DataSegment("RELOAD_DATA") eq "done\n") {
		DPWarning($Gettext->get("<b><big>Day planner encountered an error while saving events</big></b>.\n\nThere appears to be another instance of day planner running. This may lead to data loss.\n\nLeaving this instance of Day Planner open while you resolve the issues with the other instance of Day Planner will ensure your data remains safe. Closing this instance of Day Planner before it can safely write recent changes will result in data loss."));
		if ($Return eq 'OKAY') {
			$Return = 'DAEMON_RELOAD_FAILURE';
		}
	}
	return($Return);
}

# Purpose: Save the data file and redraw the needed windows
# Usage: UpdatedData();
sub UpdatedData {
	# Save the data
	SaveMainData();
	# Redraw the event list
	DrawEventlist();
	# Repopulate the upcoming events
	PopulateUpcomingEvents();
	# Redraw the calendar
	CalendarChange();
}

# Purpose: Load the calendar contents
# Usage: LoadCalendar();
sub LoadCalendar {
	if(DataLoadTest("$SaveToDir/$MainEventsFile")) {
		%CalendarContents = do("$SaveToDir/$MainEventsFile");
	}
	if(DataLoadTest("$SaveToDir/$BirthdayFile")) {
		%BirthdayContents = do("$SaveToDir/$BirthdayFile");
	}
	if(DataLoadTest("$SaveToDir/$SpecialEventsFile")) {
		%SpecialEvents = do("$SaveToDir/$SpecialEventsFile");
	}
	if(DataLoadTest("$SaveToDir/$AllDayEventsFile")) {
		%AllDayEvents = do("$SaveToDir/$AllDayEventsFile");
	}
	return(1);
}

# Purpose: Test if a load can be successfully performed of the supplied file
# Usage: DataLoadTest("/path/to/file");
sub DataLoadTest {
	# If the calendar savefile exists
	if (-e "$_[0]") {
		# If it isn't readable then we warn and return
		unless (-r "$_[0]") {
			DPIntWarn("Unable to read the calendar at $_[0]: It isn't readable by me");
			return(0);
		}
		# If it isn't writeable then we just warn (the file can still be loaded)
		unless (-w "$_[0]") {
			DPIntWarn("Unable to write the calendar at $_[0]: It isn't writeable by me. Expect trouble!");
		}
		return(1);
	} else {
		# If the file doesn't exist then we just assume that it isn't created yet,
		# so we return false, but do it silently.
		return(0);
	}
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Core helper functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Get OS/distro version information
# Usage: print "OS: ",GetDistVer(),"\n";
sub GetDistVer {
	# GNU/Linux and BSD
	foreach(qw/mandriva mandrakelinux mandrake fedora redhat red-hat ubuntu lsb debian gentoo suse distro dist slackware freebsd openbsd netbsd dragonflybsd NULL/) {
		if (-e "/etc/$_-release" or -e "/etc/$_-version" or -e "/etc/${_}_version" or $_ eq "NULL") {
			my ($DistVer, $File, $VERSION_FILE);
			if(-e "/etc/$_-release") {
				$File = "$_-release";
				open($VERSION_FILE, "<", "/etc/$_-release");
				$DistVer = <$VERSION_FILE>;
			} elsif (-e "/etc/$_-version") {
				$File = "$_-version";
				open($VERSION_FILE, "<", "/etc/$_-release");
				$DistVer = <$VERSION_FILE>;
			} elsif (-e "/etc/${_}_version") {
				$File = "${_}_version";
				open($VERSION_FILE, "<", "/etc/${_}_version");
				$DistVer = <$VERSION_FILE>;
			} elsif ($_ eq "NULL") {
				last unless -e "/etc/version";
				$File = 'version';
				open($VERSION_FILE, "<", "/etc/version");
				$DistVer = <$VERSION_FILE>;
			}
			close($VERSION_FILE);
			chomp($DistVer);
			return("/etc/$File: $DistVer");
		}
	}
	# Didn't find anything yet. Get uname info
	my ($sysname, $nodename, $release, $version, $machine) = POSIX::uname();
	if ($sysname =~ /darwin/i) {
		my $DarwinName;
		my $DarwinOSVer;
		# Darwin kernel, try to get OS X info.
		if(InPath("sw_vers")) {
			if(open(my $SW_VERS, "sw_vers |")) {
				while(<$SW_VERS>) {
					chomp;
					if (s/^ProductName:\s+//gi) {
						$DarwinName = $_;
					} elsif(s/^ProductVersion:\s+//) {
						$DarwinOSVer = $_;
					}
				}
				close($SW_VERS);
			}
		}
		if(defined($DarwinOSVer) and defined($DarwinName)) {
			return("$DarwinName $DarwinOSVer ($machine)");
		}
	}
	return("Unknown ($sysname $release $version $machine)");
}

# Purpose: Print a warning to STDERR with proper output
# Usage: DPIntWarn("Warning");
sub DPIntWarn {
	warn "*** (Day Planner $Version) Warning: $_[0]\n";
}

# Purpose: Delete keys from %CalendarContents, recusively if needed
# Usage: CalendarDelete(year,month,day,time);
sub CalendarDelete {
	my ($EventYear,$EventMonth,$EventDay,$EventTime) = @_;
	delete($CalendarContents{$EventYear}{$EventMonth}{$EventDay}{$EventTime});
		unless (keys(%{$CalendarContents{$EventYear}{$EventMonth}{$EventDay}})) {
			delete($CalendarContents{$EventYear}{$EventMonth}{$EventDay});
				unless (keys(%{$CalendarContents{$EventYear}{$EventMonth}})) {
					delete($CalendarContents{$EventYear}{$EventMonth});
						unless(keys(%{$CalendarContents{$EventYear}})) {
							delete($CalendarContents{$EventYear});
						}
			}
	}
}

# Purpose: Delete keys from %BirthdayContents, recusively if needed
# Usage: BirthdayDelete(month,day,name);
sub BirthdayDelete {
	my ($EventMonth,$EventDay,$Name) = @_;
	delete($BirthdayContents{$EventMonth}{$EventDay}{$Name});
		unless (keys(%{$BirthdayContents{$EventMonth}{$EventDay}})) {
			delete($BirthdayContents{$EventMonth}{$EventDay});
				unless (keys(%{$BirthdayContents{$EventMonth}})) {
					delete($BirthdayContents{$EventMonth});
			}
	}
}

# Purpose: Print formatted --help output
# Usage: PrintHelp("-shortoption", "--longoption", "description");
sub PrintHelp {
	printf "%-4s %-16s %s\n", "$_[0]", "$_[1]", "$_[2]";
}

# Purpose: Recursively test for defined data in %CalendarContents
# 		If a single test is run then invalid data might get inserted into %CalendarContents
# 		this avoids that by doing a recursive test
# Usage: CalendarDefined(year,month,day,time);
sub CalendarDefined {
	my ($EventYear,$EventMonth,$EventDay,$EventTime,$EventType) = @_;
	if(defined($CalendarContents{$EventYear}) and defined($CalendarContents{$EventYear}{$EventMonth}) and defined($CalendarContents{$EventYear}{$EventMonth}{$EventDay}) and defined($CalendarContents{$EventYear}{$EventMonth}{$EventDay}{$EventTime})) {
			return(1)
		}
	return(0);
}

# Purpose: Recursively test for defined data in %BirthdayContents
# 		If a single test is run then invalid data might get inserted into %BirthdayContents
# 		this avoids that by doing a recursive test
# Usage: BirthdayDefined(month,day,summary);
sub BirthdayDefined {
	my ($EventMonth, $EventDay, $EventSummary) = @_;
	if(defined($BirthdayContents{$EventMonth}) and defined($BirthdayContents{$EventMonth}{$EventDay}) and defined($BirthdayContents{$EventMonth}{$EventDay}{$EventSummary})) {
			return(1)
		}
	return(0);
}

# Purpose: Recursively test for defined data in %CalendarContents
# 		If a single test is run then invalid data might get inserted into %CalendarContents
# 		this avoids that by doing a recursive test
# Usage: CalendarDayDefined(year,month,day);
sub CalendarDayDefined {
	my ($EventYear,$EventMonth,$EventDay,$EventTime,$EventType) = @_;
	if(defined($CalendarContents{$EventYear}) and defined($CalendarContents{$EventYear}{$EventMonth}) and defined($CalendarContents{$EventYear}{$EventMonth}{$EventDay})) {
			return(1)
		}
	return(0);
}

# Purpose: Recursively test for defined data in %BirthdayContents
# 		If a single test is run then invalid data might get inserted into %BirthdayContents
# 		this avoids that by doing a recursive test
# Usage: BirthdayDayDefined(month,day);
sub BirthdayDayDefined {
	my ($EventMonth, $EventDay, $EventSummary) = @_;
	if(defined($BirthdayContents{$EventMonth}) and defined($BirthdayContents{$EventMonth}{$EventDay})) {
			return(1)
		}
	return(0);
}

# Purpose: Handle various signals gracefully
# Usage: $SIG{SIGNAL} = \&DP_SigHandler;
sub DP_SigHandler {
	$| = 1;
	print "SIG$_[0] recieved.\nSaving data...";
	if(SaveMainData() eq "SAVE_FAILED") {
		print "FAILED";
	} else {
		print "done";
	}
	print "\nWriting state file...";
	WriteStateFile($SaveToDir, "state.conf");
	print "done\nExiting the gtk2 main loop...";
	Gtk2->main_quit;
	print "done\nClosing the daemon connection...";
	CloseDaemon();
	print "done\nExiting\n";
	exit(0);
}

# Purpose: Make sure we have a .holiday set up
# Usage: HolidaySetup();
sub HolidaySetup {
	# If Holiday_Setup is true and the file exists then day planner has properly set up
	# a holiday-file.
	#
	# If Holiday_Attempted is defined and equal to $Version then we've attempted and failed
	# to set up a holiday file in this version of day planner so we'll skip trying again.
	if($InternalConfig{Holiday_Setup} and -e $HolidayFile) {
		return(1);
	} elsif(defined($InternalConfig{Holiday_Attempted}) and $InternalConfig{Holiday_Attempted} eq $Version) {
		return(1);
	}
	# Hash of LC_ADDRESS code => holiday file
	my %HolidayFiles = (
		"nb" => "norway",	# Norway
		"nn" => "norway",	# Norway
		"no" => "norway",	# Norway
		"en_US" => "us",	# USA
		"us" => "us",		# USA
		"en_UK" => "uk",	# The UK
		"en_GB" => "uk",	# The UK
		"uk" => "uk",		# The UK
		"sv" => "swedish",	# Sweden
		"fr" => "french",	# France
		"it" => "italy",	# Italy
		"el_GR" => "greek",	# Greece
		"cs_CZ" => "czech",	# Czech
		"da_DK" => "denmark",	# Denmark
		"nl_NL" => "dutch",	# The Netherlands
		"fi_FI" => "finnish",	# Finland
		"de_DE" => "german",	# Germany
		"hu_HU" => "hungary",	# Hungary
		"ja_JP" => "japan",	# Japan
		"pt_PT" => "portugal",	# Portugal
		"sk_SK" => "slovak",	# Slovakia
		"es_ES" => "spain",	# Spain
		"en_CA" => "canada",	# Canada
	);
	# Yay, the user already has a .holiday file. Use this one
	if(-e "$ENV{HOME}/.holiday") {
		symlink("$ENV{HOME}/.holiday",$HolidayFile);
		$InternalConfig{Holiday_Setup} = 1; delete($InternalConfig{Holiday_Attempted}) if defined($InternalConfig{Holiday_Attempted});
		return(1);
	}


	my $LocationDetect;

	if(defined($ENV{LC_ADDRESS})) {
		$LocationDetect = $ENV{LC_ADDRESS};
	} elsif (defined($ENV{LC_TELEPHONE})) {
		$LocationDetect = $ENV{LC_TELEPHONE};
	} elsif (defined($ENV{LC_IDENTIFICATION})) {
		$LocationDetect = $ENV{LC_IDENTIFICATION};
	} elsif (defined($ENV{LC_MESSAGES})) {
		$LocationDetect = $ENV{LC_MESSAGES};
	} elsif (defined($ENV{LC_ALL})) {
		$LocationDetect = $ENV{LC_ALL};
	} elsif (defined($ENV{LANGUAGE})) {
		$LocationDetect = $ENV{LANGUAGE};
	} else {
		DPIntWarn("Neither of the environment variables LC_ADDRESS, LC_TELEPHONE, LC_IDENTIFICATION, LC_MESSAGES, LC_ALL nor LANGUAGE was set. Unable to autodetect .holiday file");
	}

	my $CopyFile;
	
	if(defined($LocationDetect)) {
		# Let's try to find the users LC_ADDRESS in the %HolidayFiles hash
		# We sort it so that we test the longest entries before testing the short ones
		foreach my $Key (sort {length($b) <=> length($a)} keys(%HolidayFiles)) {
			if($LocationDetect =~ /^$Key/) {
			$CopyFile = $HolidayFiles{$Key};
					last;
			}
		}
		if(defined($CopyFile)) {
			if(-e "$FindBin::RealBin/holiday/holiday_$CopyFile") {
				copy("$FindBin::RealBin/holiday/holiday_$CopyFile", $HolidayFile);
				$InternalConfig{Holiday_Setup} = 1; delete($InternalConfig{Holiday_Attempted}) if defined($InternalConfig{Holiday_Attempted});
				return(1);
			} else {
				DPIntWarn("The .holiday file detected for you (holiday_$CopyFile at $FindBin::RealBin/holiday/holiday_$CopyFile) did not exist.");
			}
		} else {
			DPIntWarn("Couldn't detect a .holiday file for $LocationDetect. Maybe you would like to write one?");
		}
	}
	open(my $DUMMY_FILE, ">", "$HolidayFile");
	print $DUMMY_FILE ": This is a dummy .holiday file for day planner. It couldn't detect a proper\n";
	print $DUMMY_FILE ": one suitable for your location (which at the time was detected to be $LocationDetect).\n";
	print $DUMMY_FILE ": You may want to write one yourself (see the files contained in the holiday/ directory\n";
	print $DUMMY_FILE ":  of the day planner distribution for examples of the syntax).\n";
	print $DUMMY_FILE "\n: HOW TO REPLACE THE DUMMY FILE WITH A PROPER ONE:\n: Remove this file, copy the proper file to ~/.holiday and re-run day planner";
	close($DUMMY_FILE);
	$InternalConfig{Holiday_Setup} = 0;
	$InternalConfig{Holiday_Attempted} = $Version;
	return(0);
}

# Purpose: Remove the daemon from autostart for the various DMs/WMs
# Usage: RemoveAutostart();
sub RemoveAutostart {
	# KDE, XDG and XFCE4 - easy
	foreach("$ENV{HOME}/.kde/Autostart/dayplanner_auto.sh", "$ENV{HOME}/.config/autostart/dayplanner_auto.desktop", "$ENV{HOME}/Desktop/Autostart/dayplanner_auto.sh") {
		unlink($_) if -e $_;
	}

	# Fluxbox
	if(-w "$ENV{HOME}/.fluxbox/startup") {
		my @FluxboxStartup;
		open(my $OLDFLUX, "<", "$ENV{HOME}/.fluxbox/startup");
		push(@FluxboxStartup, $_) while(<$OLDFLUX>);
		close($OLDFLUX);
		open(my $FLUXSTART, ">", "$ENV{HOME}/.fluxbox/startup");
		foreach(@FluxboxStartup) {
			unless(/$DaemonName/) {
				chomp;
				print $FLUXSTART "$_\n";
			}
		}
		close($FLUXSTART);
	}
	# GNOME 2 versions 2.12 and older.
	#  This solution is very very hacky and unreliable, but it will have to do.
	if(-w "$ENV{HOME}/.gnome2/session-manual") {
		my @Gnome2Session;
		my $GNOME2SESS;
		open($GNOME2SESS, "<", "$ENV{HOME}/.gnome2/session-manual");
		push(@Gnome2Session, $_) while(<$GNOME2SESS>);
		close($GNOME2SESS);
		my ($Last, $Has_DP, $DP_ID, $Nullout);
		# Parse
		foreach(@Gnome2Session) {
			next unless /\=/;
			next unless /^\d,/;
			my $ID = $_;
			$ID =~ s/^(\d+).*/$1/;
			if(/$DaemonName/) {
				$DP_ID = $ID;
				$Has_DP = 1;
			}
			$Last = $ID;
			# Make sure we're not starting on yet another session.
			if($ID == 0) {
				# GAH, another session decleration. This is too much for us to parse.
				# Use the alternate nullout method (continue attempting to parse if Has_DP is
				# false
				$Nullout = 1;
				last if $Has_DP;
			}
		}
		
		# We've got three ways to do this, one more hacky than the next - but
		# as gnome2 (2.12 and older) doesn't have any good way of dealing with
		# this we'll have to do this.
		if($Has_DP) {
			if($Last == 0) {
				# Wee, LAST is 0, just 0 out the file
				open($GNOME2SESS, ">", "$ENV{HOME}/.gnome2/session-manual");
				close($GNOME2SESS);
			} elsif($Nullout) {
				DPIntWarn("Unable to properly remove the GNOME2 day planner entry from $ENV{HOME}/.gnome2/session-manual. A workaround to disable the daemon startup has been applied but you may want to use the GNOME2 session manager to remove it completely.");
				foreach(@Gnome2Session) {
					s/=.*$DaemonName.*/perl -e '# Removed by day planner'/;
				}
				open($GNOME2SESS, ">", "$ENV{HOME}/.gnome2/session-manual");
				foreach(@Gnome2Session) {
					print $GNOME2SESS $_;
				}
				close($GNOME2SESS);
			} else {
				my $NumClients = $Last + 1;
				# Write out changes
				open($GNOME2SESS, ">", "$ENV{HOME}/.gnome2/session-manual");
				foreach(@Gnome2Session) {
					chomp;
					s/^num_clients=\d+/num_clients=$Last/;
					s/^$Last,/$DP_ID,/;
					next if /^$DP_ID,/;
					print $GNOME2SESS "$_\n";
				}
				close($GNOME2SESS);
			}
		}
	}
	return(1);
}

# Purpose: Add the daemon to autostart for the various DMs/WMs
# Usage: AddAutostart(OLD_GNOME?);
# 	OLD_GNOME is either 1 or 0, defines if the GNOME version used is
# 	older than 2.13 or not
sub AddAutostart {
	my $TryGnome = $_[0];
	my $DaemonExec;
	my $RETURN = "okay";
	foreach(split(/:/, sprintf("%s:%s", dirname(Cwd::realpath($0)), $ENV{PATH} ))) {
		if (-x "$_/$DaemonName") {
			$DaemonExec = "$_/$DaemonName";
			last;
		}
	}
	die("Unable to detect the daemon") unless(defined($DaemonExec));

	# KDE - this is easy
	if(-d "$ENV{HOME}/.kde/") {
		mkdir("$ENV{HOME}/.kde/Autostart/") unless -e "$ENV{HOME}/.kde/Autostart/";
		open(my $KDESCRIPT, ">", "$ENV{HOME}/.kde/Autostart/dayplanner_auto.sh");
		print $KDESCRIPT "#!/bin/sh\n# Autogenerated startup script written by day planner\n";
		print $KDESCRIPT "$DaemonExec\n";
		close($KDESCRIPT);
	}
	# Freedesktop spec - this is easy too
	if(-d "$ENV{HOME}/.config/") {
		mkdir("$ENV{HOME}/.config/autostart/") unless -e "$ENV{HOME}/.config/autostart";
		open(my $XDGSTART, ">", "$ENV{HOME}/.config/autostart/dayplanner_auto.desktop");
		print $XDGSTART "# Autogenerated startup desktop file written by day planner\n";
		print $XDGSTART "[DESKTOP ENTRY]\n";
		print $XDGSTART "Type=Application\n";
		print $XDGSTART "Name=Day planner daemon autostart\n";
		print $XDGSTART "Comment=Automatically start the day planner daemon on session startup\n";
		print $XDGSTART "Exec=$DaemonExec\n";
		print $XDGSTART "StartupNotify=false\n";
		print $XDGSTART "StartupWMClass=fales\n";
		close($XDGSTART);
	}
	# GNOME - this is harder. No real common spec or easy way to do it. 2.14 or so
	#  supports the freedesktop spec, older however - does not.
	if(-d "$ENV{HOME}/.gnome2/" and $TryGnome) {
		# If it doesn't exist then we just write it
		unless(-e "$ENV{HOME}/.gnome2/session-manual") {
			open(my $GNOMESESS, ">", "$ENV{HOME}/.gnome2/session-manual");
			print $GNOMESESS "[Default]\nnum_clients=1\n0,RestartStyleHint=3\n0,Priority=50\n0,RestartCommand=$DaemonExec\n0,Program=$DaemonExec";
			close($GNOMESESS);
		} else {
			$RETURN = "gnome-fail";
			my @G2H;
			open(my $GNOMESESS, "<", "$ENV{HOME}/.gnome2/session-manual");
			while(<$GNOMESESS>) {
				if(s/perl -e '# Removed by day planner'/$DaemonExec/i) {
					$RETURN = "okay";
				}
				push(@G2H, $_);
			}
			close($GNOMESESS);
			if($RETURN eq "okay") {
				open($GNOMESESS, ">", "$ENV{HOME}/.gnome2/session-manual");
				print $GNOMESESS $_ foreach(@G2H);
				close($GNOMESESS);
			}
		}
	}
	# XFce 4
	if(-d "$ENV{HOME}/.config/xfce4/" and -d "$ENV{HOME}/Desktop/") {
		mkdir("$ENV{HOME}/Desktop/Autostart") unless -e "$ENV{HOME}/Desktop/Autostart";
		open(my $XFCESCRIPT, ">", "$ENV{HOME}/Desktop/Autostart/dayplanner_auto.sh");
		print $XFCESCRIPT "#!/bin/sh\n# Autogenerated startup script written by day planner\n";
		print $XFCESCRIPT "$DaemonExec\n";
		close($XFCESCRIPT);
	}
	# Fluxbox
	if(-d "$ENV{HOME}/.fluxbox") {
		my @FluxboxStartup;
		push(@FluxboxStartup, "$DaemonExec &");
		if(-e "$ENV{HOME}/.fluxbox/startup") {
			open(my $OLDFLUX, "<", "$ENV{HOME}/.fluxbox/startup");
			push(@FluxboxStartup, $_) while(<$OLDFLUX>);
			close($OLDFLUX);
		}
		open(my $FLUXSTART, ">", "$ENV{HOME}/.fluxbox/startup");
		foreach(@FluxboxStartup) {
			chomp;
			print $FLUXSTART "$_\n";
		}
		close($FLUXSTART);
	}
	return($RETURN);
}

# Purpose: Get the number of *milli*seconds until midnight
# Usage: my $miliseconds = MilisecondsUntilMidnight();
sub MilisecondsUntilMidnight {
	my ($currsec,$currmin,$currhour,$currmday,$currmonth,$curryear,$currwday,$curryday,$currisdst) = localtime(time);
	# Convert hours to seconds
	$currhour = $currhour * 60 * 60;
	# Minutes to seconds
	$currmin = $currmin * 60;

	my $SecondsInADay = 86_400;
	my $total = $SecondsInADay - ($currhour + ($currmin + $currsec));
	return($total*1000);
}

# Purpose: Repopulate the UpcomingEvents widget on a timer (every 24h)
# Usage: \&Day_Changed_Event;
sub Day_Changed_Event {
	# First repopulate the upcoming events widget
	PopulateUpcomingEvents();
	# Now reset the timer
	Set_DayChangeTimer();
	# Return false to make Glib remove the old timer
	return(0);
}

# Purpose: Set the day-changed timer
# Usage: Set_DayChangeTimer();
sub Set_DayChangeTimer {
	# Set the timer
	Glib::Timeout->add(MilisecondsUntilMidnight(), \&Day_Changed_Event);
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# GUI helper functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Populate the upcoming events widget
# Usage: PopulateUpcomingEvents();
sub PopulateUpcomingEvents {
	my $NewUpcoming;
	my $HasUpcoming;
	my %InformationHash;
	my %DayNames = (
		0 => $Gettext->get("Sunday"),
		1 => $Gettext->get("Monday"),
		2 => $Gettext->get("Tuesday"),
		3 => $Gettext->get("Wednesday"),
		4 => $Gettext->get("Thursday"),
		5 => $Gettext->get("Friday"),
		6 => $Gettext->get("Saturday")
	);

	# Today is
	my ($getsec,$getmin,$gethour,$getmday,$getmonth,$getyear,$getwday,$getyday,$getisdst) = localtime(time);
	my $Year = $getyear;

	# Prepare
	my $TheYday = $getyday;
	my $FirstDay = 1;
	my $AddDays = 7;
	$TheYday++; # Yearday currently starts at 0, we need it to start at 1

	# Loop used to populate the %InformationHash
	while($AddDays) {
		$TheYday++;	# This is a new day
		$AddDays--;	# One less day to add

		$InformationHash{$TheYday} = {};
		my $h = $InformationHash{$TheYday};
		
		my $Mktime = POSIX::mktime(0, 0, 0, $TheYday, 0, $Year);	# Get the POSIX time of this day

		my ($getsec,$getmin,$gethour,$getmday,$getmonth,$getyear,$getwday,$getyday,$getisdst) = localtime($Mktime);	# Get the real time of this day
		$getmonth++;	# Month should be 1-12 not 0-11

		my $HumanYear = $getyear+1900;	# Human readable year

		if($FirstDay) {
			$h->{text}= $Gettext->get("Tomorrow");
			$h->{dayname} = $h->{text};
			$FirstDay = 0;
		} else {
			$h->{text} .= "\n\n";
			$h->{text} .= $DayNames{$getwday};
			$h->{dayname} = $DayNames{$getwday};
		}
		$h->{date} = "$getmday.$getmonth.$HumanYear";
		$h->{text} .= " ($getmday.$getmonth.$HumanYear) :\n ";
		my $HasEvents;
		if(CalendarDayDefined($Year+1900, $getmonth, $getmday)) {
			foreach(keys(%{$CalendarContents{$HumanYear}{$getmonth}{$getmday}})) {
				$HasEvents = 1;
				$h->{text} .= $Gettext->get("At") . " $_: $CalendarContents{$HumanYear}{$getmonth}{$getmday}{$_}{summary}";
			}
		}
		if(BirthdayDayDefined($getmonth, $getmday)) {
			foreach my $Birthday (keys(%{$BirthdayContents{$getmonth}{$getmday}})) {
					$HasEvents = 1;
					$h->{text} .= sprintf($Gettext->get("%s's birthday"), $Birthday);
			}
		}
		unless($HasEvents) {
			$h->{text} .= $Gettext->get("(nothing)");
			$h->{noevents} = 1;
		} else {
			$HasUpcoming = 1;
		}

	}
	unless($HasUpcoming) {
		$NewUpcoming = $Gettext->get("You have no upcoming events the next seven days");
	} else {
		my $LoopNum;
		# Remove duplicate (nothing)'s
		foreach my $key (sort(keys(%InformationHash))) {
			# If the key doesn't exist (any more) or doesn't have noevents then skip it
			next unless(defined($InformationHash{$key}));
			$LoopNum++;		# Up the loop counter
			next unless(defined($InformationHash{$key}{noevents}));
			# Find out which key is next
			my $Next = $key;
			$Next++;
			# Skip if the next key doesn't have noevents set
			next unless(defined($InformationHash{$Next}) and defined($InformationHash{$Next}{noevents}));

			my @OtherNoevents;	# Array of the next keys without any events
			my $LastDate;
			my $LastDay;		# The last day with noevents

			# For each of the next dates with no events set it up for usage here and if we had another
			# noevents before this push it onto the @OtherNoevents array and replace the $LastDay value
			# with ours.
			while(defined($InformationHash{$Next}) and defined($InformationHash{$Next}{noevents})) {
				$LastDate = $InformationHash{$Next}{date};
				if(defined($LastDay)) {
					push(@OtherNoevents, $LastDay);
				}
				$LastDay = $Next;
				$Next++;
			}
			# Reset the current text 
			if($LoopNum > 1) {
				$InformationHash{$key}{text} = "\n\n";
			} else {
				$InformationHash{$key}{text} = "";
			}
			# If there is something in @OtherNoevents then do more processing
			if(@OtherNoevents) {
				# First day (current key)
				$InformationHash{$key}{text} .= "$InformationHash{$key}{dayname},";

				my $Counter;	# Count how many times we've gone through the foreach
				foreach(@OtherNoevents) {
					$Counter++;	# Up the counter
					$InformationHash{$key}{text} .= " $InformationHash{$_}{dayname}";
					unless($Counter eq scalar(@OtherNoevents)) {	# If the counter doesn't equal the number of entries
											# in the array then append a comma.
						$InformationHash{$key}{text} .= ",";
					}
					# Delete the key
					delete($InformationHash{$_});
				}
				# Append the last entries
				$InformationHash{$key}{text} .= " " . $Gettext->get("and") . " $InformationHash{$LastDay}{dayname}";
			} else {
				# Build the string
				$InformationHash{$key}{text} .= "$InformationHash{$key}{dayname} " . $Gettext->get("and") . " $InformationHash{$LastDay}{dayname}";
			}
			# Delete the $LastDay key
			delete($InformationHash{$LastDay});
			# Finalize the string
			$InformationHash{$key}{text} .= " ($InformationHash{$key}{date}-$LastDate): " . $Gettext->get("(nothing)");
		}
		# Build our $NewUpcoming
		foreach my $key(sort(keys(%InformationHash))) {
			$NewUpcoming .= $InformationHash{$key}{text};
		}
	}
	# Don't update the widget if the text hasn't changed
	unless($UpcomingEventsWidget->get_buffer eq $NewUpcoming) {
		$UpcomingEventsBuffer->set_text($NewUpcoming);
	}
}

# Purpose: Wrapper around AddAutostart that detects if we have an pre-2.14
# 		version of GNOME and displays an information dialog about the
# 		inability to add a gnome autostart
# Usage: DP_AddAutostart();
sub DP_AddAutostart {
	my $GNOME_Version;
	my $Old_GNOME;

	# Set the settings var
	$InternalConfig{AutostartOn} = 1;

	# Try to get the gnome version from the gnome control center
	foreach(split(/:/, sprintf("%s:%s", dirname(Cwd::realpath($0)), $ENV{PATH} ))) {
		if (-x "$_/gnome-control-center") {
			$GNOME_Version = qx#$_/gnome-control-center --version#;
			chomp($GNOME_Version);
			$GNOME_Version =~ s/^(.+)\s+(.+)\s+(.+)$/$3/;
			$GNOME_Version =~ s/^(\d+\.\d+).*$/$1/;
			$GNOME_Version = undef unless($GNOME_Version =~ /^\d+\.\d+(\.\d+.*)?$/);
			last;
		}
	}
	if(defined($GNOME_Version)) {
		unless($GNOME_Version > 2.12) {
			$Old_GNOME = 1;
		} else {
			$Old_GNOME = 0;
		}
	} else {	# If we couldn't get the GNOME version then we just assume the user 
			# either doesn't use GNOME or has a version of GNOME newer than 2.12
		$Old_GNOME = 0;
	}

	my $Result = AddAutostart($Old_GNOME);
	return(1) if $Result =~ /^okay$/;

	if($Result =~ /^gnome-fail$/) {
		# GNOME failed, check if GNOME_DESKTOP_SESSION_ID is set.
		# If it isn't set, then the user isn't running GNOME *now* so we don't want to display the
		# error.
		if(defined($ENV{GNOME_DESKTOP_SESSION_ID}) and length($ENV{GNOME_DESKTOP_SESSION_ID})) {
			# It's set, so the user is running GNOME - display the error
			DPError($Gettext->get("A problem occurred while setting up automatic startup of the day planner reminder.\n\nYou will have to set it up manually. Do this by selecting: menu -> desktop -> settings -> sessions. From there select \"Startup Programs\", click \"Add\". Type \"dayplanner-daemon\" in the field \"Startup Command\" and press \"OK\". It is now set up."));
		}
	} else {
		DPIntWarn("Unknown return value from AddAutostart(): $Result");
	}
	return(1);
}

# Purpose: Wrapper around RemoveAutostart that sets the config vars
# Usage: DP_RemoveAutostart();
sub DP_RemoveAutostart {
	if(DPQuestion($Gettext->get("Are you sure you want to disable the automatic startup of the reminder?\n\nBy doing this you will not get notified about events without having started day planner at least once."))) {
		$InternalConfig{AutostartOn} = 1;
		return(RemoveAutostart());
	}
	return(undef);
}

# Purpose: Detect the path to the image file(s) supplied. Returns the path to the
# 		first one found or undef
# Usage: $Image = DetectImage(image1, image2);
sub DetectImage {
	my $I_Am_At = dirname(Cwd::realpath($0));
	foreach my $Image (@_) {
		foreach my $Dir ("$I_Am_At/art", $I_Am_At, "/usr/share/dayplanner", "/usr/local/dayplanner", "/usr/local/share/dayplanner", "/usr/share/dayplanner/art", "/usr/local/dayplanner/art", "/usr/local/share/dayplanner/art", "/usr/share/icons/large", "/usr/share/icons", "/usr/share/icons/mini") {
			if (-e "$Dir/$Image") {
				return("$Dir/$Image");
			}
		}
	}
	return(undef);
}

# Purpose: Delete the event currently selected in the eventlist
# Usage: DeleteEvent();
#  (calls CalendarDelete with the correct parameters)
sub DeleteEvent {
	my $Selected = [$EventlistWidget->get_selected_indices]->[0];
	# Unless $Selected is defined we don't have anything selected in the eventlist
	unless(defined($Selected)) {
		DPIntWarn("DeletEvent() called without any event selected");
		return(0);
	}
	my $Type = GetEventListType();
	my ($EventYear, $EventMonth, $EventDay) = $CalendarWidget->get_date();$EventMonth++;
	my $EventTime = $EventlistWidget->{data}[$Selected][0];
	my $EventSummary = $EventlistWidget->{data}[$Selected][1];

	if($Type eq 'normal') {
		if (defined($EventTime)) {
			CalendarDelete($EventYear,$EventMonth,$EventDay,$EventTime);
			DrawEventlist();
			CalendarChange();
			SaveMainData();
		}
	} elsif ($Type eq 'bday') {
		if (defined($EventSummary)) {
			BirthdayDelete($EventMonth, $EventDay, GetRealBirthdayName($EventSummary));
			DrawEventlist();
			CalendarChange();
			SaveMainData();
		}
	} else {
		DPIntWarn("DeleteEvent: Attempted to handle unsupported \$Type: $Type");
	}
}

# Purpose: Get the real name of the person whose birthday it is
# Usage: my $Name = GetRealBirthdayName(NAME);
sub GetRealBirthdayName {
	my $Name = $_[0];
	my $BirthdayString = $Gettext->get("%s's birthday");
	$BirthdayString =~ s/%s//;
	$Name =~ s/$BirthdayString$//;
	return($Name);
}

# Purpose: Detect which kind of event is selected in the eventlist
# Usage: my $Type = GetEventListType();
sub GetEventListType {
	my $Selected = [$EventlistWidget->get_selected_indices]->[0];
	# If $Selected isn't defined then nothing *is* selected and thus we just return (false)
	unless(defined($Selected)) {
		return(0);
	}
	# Initialize
	my $Time = $EventlistWidget->{data}[$Selected][0];
	my $Summary = $EventlistWidget->{data}[$Selected][1];
        my ($EventYear, $EventMonth, $EventDay) = $CalendarWidget->get_date();$EventMonth++;

	my $BirthdayString = $Gettext->get("%s's birthday");
	$BirthdayString =~ s/%s//g;

	# Detect the event type
	if (defined($Time) and length($Time)) {
		return('normal');
	} elsif (defined($Summary) and length($Summary) and $Summary =~ /$BirthdayString/) {
		return('bday')
	} elsif (defined($Summary) and length($Summary)) {
		return('holiday')
	} else {
		DPIntWarn("FATAL: GetEventListType: Unable to get the type of the selected event ($EventYear-$EventMonth-$EventDay-$Time/$Summary");
		return(0);
	}
}

# Purpose: Close a window when the escape key is pressed
# Usage: $WIDGET->signal_connect("key_release_event" => \&EscapeKeyHandler);
sub EscapeKeyHandler {
	my ($widget, $event) = @_;
	if ($event->keyval == $Gtk2::Gdk::Keysyms{Escape}) {
		$widget->destroy();
	}
}

# Purpose: Display an error dialog
# Usage: DPError("Error message");
sub DPError {
	my $Dialog = Gtk2::MessageDialog->new($MainWindow, "modal", 'error', 'ok', "$_[0]");
	$Dialog->run();
	$Dialog->destroy();
}

# Purpose: Display a warning dialog
# Usage: DPWarning("Warning");
sub DPWarning {
	my $Dialog = Gtk2::MessageDialog->new_with_markup($MainWindow, "modal", 'warning', 'ok', "$_[0]");
	$Dialog->run();
	$Dialog->destroy();
}

# Purpose: Display an information dialog
# Usage: DPInfo("Information message");
sub DPInfo {
	my $Dialog = Gtk2::MessageDialog->new($MainWindow, "modal", 'info', 'ok', "$_[0]");
	$Dialog->run();
	$Dialog->destroy();
}

# Purpose: Display a question dialog
# Usage: DPQuestion("Question");
# 	Returns true on yes, false on anything else
sub DPQuestion {
	my $Dialog = Gtk2::MessageDialog->new(undef, "modal", 'question', 'yes-no', $_[0]);
	my $Reply = $Dialog->run();
	$Dialog->destroy();
	if ($Reply eq 'yes') {
		return(1);
	} else {
		return(0);
	}
}

# Purpose: Call save functions on exit
# Usage: QuitSub();
sub QuitSub {
	$MainWindow->hide();
	my $SaveData = SaveMainData();
	if ($SaveData eq 'SAVE_FAILED') {
		unless(DPQuestion($Gettext->get("Some files could not be saved correctly. Are you sure you want to quit?"))) {
			$MainWindow->show();
			return(1);
		}
	} elsif ($SaveData eq 'DAEMON_RELOAD_FAILURE') {
		unless(DPQuestion($Gettext->get("Are you sure you still want to quit?"))) {
			$MainWindow->show();
			return(1);
		}
	}
	
	WriteStateFile($SaveToDir, "state.conf");
	
	Gtk2->main_quit;
	CloseDaemon();
	exit(0);
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# GUI calendar functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Sets the active calendar items in the $CalendarWidget
# Usage: SetActiveCalItems(YEAR, NUMERICAL_MONTH[1-12]);
sub SetActiveCalItems {
	$CalendarWidget->clear_marks;			# Clear the current marks
	# Calendar contents
	if (defined($CalendarContents{$_[0]}) and defined($CalendarContents{$_[0]}{$_[1]})) {
		foreach my $Day (keys %{$CalendarContents{$_[0]}{$_[1]}}) {
			$CalendarWidget->mark_day($Day);	# Mark this day
		}
	}
	# Birthdays
	if (defined($BirthdayContents{$_[1]})) {
		foreach my $Day (keys %{$BirthdayContents{$_[1]}}) {
			$CalendarWidget->mark_day($Day);	# Mark this day
		}
	}
}

# Purpose: Sets the date/time variables to the current date/time
# Usage: GetDate();
sub GetDate {
	($currsec,$currmin,$currhour,$currmday,$currmonth,$curryear,$currwday,$curryday,$currisdst) = localtime(time);
	$curryear += 1900;						# Fix the year format
	$currmonth++;							# Fix the month format
}

# Purpose: Calls SetActiveCalItems on the current year/month displayed in the $CalendarWidget
# Usage: CalendarChange();
sub CalendarChange {
	my $Month = $CalendarWidget->month;
	$Month++;
	SetActiveCalItems($CalendarWidget->year, $Month);
}

# Purpose: Get the day, month and year in a single string from a calendar
# Usage: my $Date = Get_DateInfo($CALWIDGET);
sub Get_DateInfo {
	my ($year, $month, $day) = $CalendarWidget->get_date();
	return("$year$month$day");
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# GUI event adding and editing functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Get a properly formatted event time from two widgets (min/hour)
# Usage: my $Time = GetTimeFromWidgets($HourSpinner, $MinuteSpinner);
sub GetTimeFromWidgets {
	# In the future this sub will take a third argument, which is either an AM/PM
	# selection box, or undef - so that seamless AM/PM support is needed.
	
	my ($HourSpinner, $MinuteSpinner) = @_;	
	my $Hour = $HourSpinner->get_value_as_int();
	my $Minute = $MinuteSpinner->get_value_as_int();
	if ($Hour <= 9) {
		$Hour = "0$Hour";
	}
	if ($Minute <= 9) {
		$Minute = "0$Minute";
	}
	return("$Hour:$Minute");
}

# Purpose: Create the widgets for selecting the time
# Usage: my ($HourSpinner, $MinuteSpinner, $TimeHBox) = TimeSelection("HH:MM");
sub TimeSelection {
	my $Time = $_[0];
	# The hour adjustment
	my $HourAdjustment = Gtk2::Adjustment->new(0.0, 0.0, 23.0, 1.0, 5.0, 0.0);
	# The minute adjustment
	my $MinuteAdjustment = Gtk2::Adjustment->new(0.0, 0.0, 59.0, 1.0, 5.0, 0.0);
	
	# Create the spinners
	my $HourSpinner = Gtk2::SpinButton->new($HourAdjustment, 0, 0);
	my $MinuteSpinner = Gtk2::SpinButton->new($MinuteAdjustment, 0, 0);
	# Show them
	$HourSpinner->show();
	$MinuteSpinner->show();
	
	# Create a simple seperating label
	my $TimeSeperatorLabel = Gtk2::Label->new(' : ');
	$TimeSeperatorLabel->show();
	
	# Make them activate the default action
	$HourSpinner->set_activates_default(1);
	$MinuteSpinner->set_activates_default(1);
	
	# Get the time
	my $HSTime = $Time;		# Hour
	my $MSTime = $Time;		# Minute
	$HSTime =~ s/^(\d+):\d+$/$1/;
	$MSTime =~ s/^\d+:(\d+)$/$1/;
	# Set the initial value
	$HourSpinner->set_value($HSTime);
	$MinuteSpinner->set_value($MSTime);
	
	# Create a HBox and pack them onto it
	my $TimeSpinnerHBox = Gtk2::HBox->new(0,0);
	$TimeSpinnerHBox->pack_start($HourSpinner,0,0,0);
	$TimeSpinnerHBox->pack_start($TimeSeperatorLabel,0,0,0);
	$TimeSpinnerHBox->pack_start($MinuteSpinner,0,0,0);
	$TimeSpinnerHBox->show();

	# Return the widgets
	return($HourSpinner, $MinuteSpinner, $TimeSpinnerHBox);
}

# Purpose: Create the window that will contain the event editor
# Usage: my ($Window, $VBox_HBoxContainer, $OKButton, $CancelButton) = CreateEventContainerWin(TITLE, LABEL, OK_BUTTON_TYPE);
#
# TITLE is the title of the window, LABEL is the label to display on top of the window.
# OK_BUTTON_TYPE is the Gtk2::Stock ID to use for the button
sub CreateEventContainerWin {
	# Get the options passed to the sub
	my ($WindowTitle, $EventLabel, $OkButtonType) = @_;
	
	# Get the date
	my ($EventYear, $EventMonth, $EventDay) = $CalendarWidget->get_date();$EventMonth++;

	# ==================================================================
	# BUILD THE WINDOW
	# ==================================================================
	my $AddEventBox = Gtk2::Window->new();
	$AddEventBox->set_modal(1);
	$AddEventBox->set_transient_for($MainWindow);
	$AddEventBox->set_position('center-on-parent');
	$AddEventBox->set_title($WindowTitle);
	$AddEventBox->set_resizable(0);
	$AddEventBox->set_border_width(5);
	$AddEventBox->set_skip_taskbar_hint(1);
	$AddEventBox->set_skip_pager_hint(1);

	# Handle closing
	$AddEventBox->signal_connect("destroy" => sub { $AddEventBox->destroy; $MainWindow->set_sensitive(1) });
	$AddEventBox->signal_connect("delete-event" => sub { $AddEventBox->destroy; $MainWindow->set_sensitive(1) });
	
	# Primary vbox
	my $ADPrimVBox = Gtk2::VBox->new();
	$AddEventBox->add($ADPrimVBox);
	$ADPrimVBox->show();
	
	# Add the label with the name
	my $DateLabel = Gtk2::Label->new(sprintf($EventLabel,$EventDay, $MonthNames{$EventMonth},$EventYear));
	$DateLabel->show();
	$ADPrimVBox->pack_start($DateLabel,1,0,0);
	
	# The HBox for the frame
	my $FrameHBox = Gtk2::HBox->new();
	$FrameHBox->show();
	$ADPrimVBox->pack_start($FrameHBox,1,1,0);
	
	# Create the frame
	my $TBFrame = Gtk2::Frame->new();
	$TBFrame->set_label($Gettext->get("Event"));
	$FrameHBox->pack_start($TBFrame,1,1,0);
	$TBFrame->show();
	
	# TODO: Add a pop-up where you can select the date (DatePopup())
	
	# Create the vbox for use in the frame
	my $TBVBox = Gtk2::VBox->new();
	$TBFrame->add($TBVBox);
	$TBVBox->show();
	
	# ==================================================================
	# Call the functions that constructs the main window contents
	# ==================================================================
	
	# Create the hbox for the buttons
	my $TB_ButtonHBox = Gtk2::HBox->new(0,6);
	$TB_ButtonHBox->show();
	$ADPrimVBox->pack_end($TB_ButtonHBox,0,0,0);
	
	# Create the OK button
	my $OKButton = Gtk2::Button->new_from_stock($OkButtonType);
	$OKButton->show();
	$TB_ButtonHBox->pack_end($OKButton,0,0,0);
	$OKButton->can_default(1);

	# Create the cancel button
	my $CancelButton = Gtk2::Button->new_from_stock('gtk-cancel');
	$CancelButton->signal_connect("clicked" => sub { $AddEventBox->destroy;
		});
	$CancelButton->show();
	$TB_ButtonHBox->pack_end($CancelButton,0,0,0);
	
	# Handle the esc button
	$AddEventBox->signal_connect("key_release_event" => \&EscapeKeyHandler);
	$AddEventBox->set_default($OKButton);

	# Return the widgets
	return($AddEventBox, $TBVBox, $OKButton, $CancelButton);
}

# Purpose: Add a event. Creates the main window and calls the proper event functions
# Usage: AddEvent();
sub AddEvent {
	$MainWindow->set_sensitive(0);
        my ($EventYear, $EventMonth, $EventDay) = $CalendarWidget->get_date();$EventMonth++;
	my $OKSignal;
	my %TimeHash = (
		EventYear => $EventYear,
		EventMonth => $EventMonth,
		EventDay => $EventDay,
	);
		
	# Create the main window with the appropriate OK/Cancel buttons and the event frame
	my ($Window, $VBox_HBoxContainer, $OKButton, $CancelButton) = CreateEventContainerWin($Gettext->get("Add an event"), $Gettext->get('Adding an event on the %s. %s %s'), 'gtk-add');
	# HBox
	my $SelectorHBox = Gtk2::HBox->new();
	$VBox_HBoxContainer->pack_start($SelectorHBox,0,0,0);
	$SelectorHBox->show();
	# Label
	my $EventTypeLabel = Gtk2::Label->new($Gettext->get("Event type:"));
	$SelectorHBox->pack_start($EventTypeLabel,0,0,0);
	$EventTypeLabel->show();
	# Combo selector
	my $EventType_Combo = Gtk2::ComboBox->new_text;
	$EventType_Combo->insert_text(0, $Gettext->get("Normal"));
	$EventType_Combo->insert_text(1, $Gettext->get("Birthday"));
	$SelectorHBox->pack_start($EventType_Combo,0,0,0);
	$EventType_Combo->show();

	# Create the tooltips widget
	my $Tooltips = Gtk2::Tooltips->new();
	$Tooltips->enable();
	# Set the tooltips
	$Tooltips->set_tip($OKButton, $Gettext->get("Add this event"));
	$Tooltips->set_tip($CancelButton, $Gettext->get("Discard this event"));
	
	# Create the widgets for the normal event selection
	my ($NE_MainWidget, $NE_HourSpinner, $NE_MinuteSpinner, $NE_SummaryWidget, $NE_DetailsWidget) = NormalEventWindow("NULL", $OKButton, $VBox_HBoxContainer, $Window);
	# Create the widgets for the birthday event selection
	my ($BE_MainWidget, $BE_NameWidget) = BirthdayEventWindow(undef, $VBox_HBoxContainer, $Window);

	# Handle changed values in the combo box
	$EventType_Combo->signal_connect('changed' => sub {
			my $ActiveIndex = $EventType_Combo->get_active;
			if ($ActiveIndex == 0) {
				$BE_MainWidget->hide();
				$NE_MainWidget->show();
				$OKButton->signal_handler_disconnect($OKSignal) if defined($OKSignal);
				$OKSignal = $OKButton->signal_connect('clicked' => sub {
					NormalEvent_OK($Window, $NE_HourSpinner, $NE_MinuteSpinner, $NE_SummaryWidget, $NE_DetailsWidget, \%TimeHash, 0);
				});
			} elsif ($ActiveIndex == 1) {
				$NE_MainWidget->hide();
				$BE_MainWidget->show();
				$OKButton->signal_handler_disconnect($OKSignal) if defined($OKSignal);
				$OKSignal = $OKButton->signal_connect('clicked' => sub {
					BirthdayEvent_OK($Window, $BE_NameWidget, \%TimeHash, 0)});
			}
		});

	$EventType_Combo->set_active(0);
	
	# Show the window
	$Window->show();
}

# Purpose: Edit the event currently selected in the eventlist
# Usage: EditEvent ();
sub EditEvent {
	my $Selected = [$EventlistWidget->get_selected_indices]->[0];
	# If $Selected isn't defined then nothing *is* selected and thus we just return (false)
	unless(defined($Selected)) {
		DPIntWarn("EditEvent() called with no event selected");
		return(0);
	}
	
	$MainWindow->set_sensitive(0);

	# Initialize
	my $Time = $EventlistWidget->{data}[$Selected][0];
	my $Summary = $EventlistWidget->{data}[$Selected][1];
	my $Tooltips = Gtk2::Tooltips->new();
	$Tooltips->enable();
        my ($EventYear, $EventMonth, $EventDay) = $CalendarWidget->get_date();$EventMonth++;
	my %TimeHash = (
		EventYear => $EventYear,
		EventMonth => $EventMonth,
		EventDay => $EventDay,
		OldEventYear => $EventYear,
		OldEventMonth => $EventMonth,
		OldEventDay => $EventDay,
		OldEventTime => $Time,
	);
		
	# Create the main window with the appropriate OK/Cancel buttons and the event frame
	my ($Window, $VBox_HBoxContainer, $OKButton, $CancelButton);

	my $Type = GetEventListType();

	if ($Type eq 'normal') {
		# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		# NORMAL EVENT
		# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		
		if(CalendarDefined($EventYear,$EventMonth,$EventDay,$Time)) {
			($Window, $VBox_HBoxContainer, $OKButton, $CancelButton) = CreateEventContainerWin($Gettext->get("Editing an event"), $Gettext->get('Editing an event on the %s. %s %s'), 'gtk-ok');

			# Create the widgets for the normal event selection
			my ($NE_MainWidget, $NE_HourSpinner, $NE_MinuteSpinner, $NE_SummaryWidget, $NE_DetailsWidget) = NormalEventWindow($Time, $OKButton, $VBox_HBoxContainer, $Window);
	
			$OKButton->signal_connect('clicked' => sub {
					NormalEvent_OK($Window, $NE_HourSpinner, $NE_MinuteSpinner, $NE_SummaryWidget, $NE_DetailsWidget, \%TimeHash, 1);
			});
		
			# Show the default widget (Normal event)
			$NE_MainWidget->show();
		} else {
			DPIntWarn("BUG!: EditEvent: Unable to edit $Type event: $EventYear/$EventMonth/$EventDay/$Time. Please report this");
			$MainWindow->set_sensitive(1);
			return(0);
		}
	} elsif ($Type eq 'bday') {
		# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		# BIRTHDAY EVENT
		# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		($Window, $VBox_HBoxContainer, $OKButton, $CancelButton) = CreateEventContainerWin($Gettext->get("Editing a birthday"), $Gettext->get('Editing a birthday on the %s. %s %s'), 'gtk-ok');
		
		$Summary = GetRealBirthdayName($Summary);
		$TimeHash{OldEventSummary} = $Summary;
		if(BirthdayDefined($EventMonth, $EventDay, $Summary)) {
			# Create the widgets for the birthday event selection
			my ($BE_MainWidget, $BE_NameWidget) = BirthdayEventWindow($Summary, $VBox_HBoxContainer, $Window);
			$BE_MainWidget->show();
			$OKButton->signal_connect('clicked' => sub { BirthdayEvent_OK ($Window, $BE_NameWidget, \%TimeHash, 1)});
		} else {
			DPIntWarn("BUG!: EditEvent: Unable to edit $Type event: $EventMonth/$EventDay/$Summary. Please report this");
			$MainWindow->set_sensitive(1);
			return(0);
		}
			
	} elsif ($Type eq 'holiday') {
		DPInfo(sprintf($Gettext->get("This event is a \"holiday\" event. It is a predefined event read from a special \"holiday\"-file. You can not edit this information directly from day planner.\n\nIf you really want to edit this file you can do so by pointing a text editor to %s"), $HolidayFile));
		$MainWindow->set_sensitive(1);
		return(1);
	} else {
		DPIntWarn("BUG!: EditEvent: \$Type contained invalid value: $Type");
		$MainWindow->set_sensitive(1);
		return(0);
	}
	
	$Tooltips->set_tip($CancelButton, $Gettext->get("Discard changes"));
	$Tooltips->set_tip($OKButton, $Gettext->get("Accept changes"));
			
	# Show the window
	$Window->show();
}

# Purpose: Create the widgets for editing a normal(events.dpd) event (%CalendarContents)
# Usage: my ($NormalEventWidget, $HourSpinner, $MinuteSpinner, $SummaryWidget, $DetailsWidget) = NormalEventWindow(TIME, OK_BUTTON, VBOX_WIDGET, MAIN_WINDOW_WIDGET);
#
# TIME is the time you're editing or undef
# OK_BUTTON is the OK button widget to attach to
# VBOX_WIDGET is the widget you want to pack our table into
# MAIN_WINDOW_WIDGET is the main window it will live inside
sub NormalEventWindow {
	# ==================================================================
	# INITIALIZE
	# ==================================================================
	my ($EventTime, $OKButton, $ParentVBox, $MyWindow) = @_;

	my $IsEditing = 0;
	
	my ($EventYear, $EventMonth, $EventDay) = $CalendarWidget->get_date();$EventMonth++;
	my ($EventSummary, $EventFulltext);
	my ($OldEventYear, $OldEventMonth,$OldEventDay,$OldEventTime);
	
	# Check if the event is already defined, if it is then we assume that we're editing
	if(CalendarDefined($EventYear,$EventMonth,$EventDay,$EventTime)) {
		# We assume these two aren't empty. This will need to change if another part becomes obligatory
		$EventSummary = $CalendarContents{$EventYear}{$EventMonth}{$EventDay}{$EventTime}{"summary"};
		$EventFulltext = $CalendarContents{$EventYear}{$EventMonth}{$EventDay}{$EventTime}{"fulltext"};
		($OldEventYear, $OldEventMonth,$OldEventDay,$OldEventTime) = ($EventYear,$EventMonth,$EventDay,$EventTime);
		$IsEditing = 1;
	}

	if ($EventTime eq 'NULL') {
		$EventTime = "00:00";
	}

	# Create the tooltips widget
	my $Tooltips = Gtk2::Tooltips->new();
	$Tooltips->enable();
	
	# Create our main vbox
	my $MainVBox = Gtk2::VBox->new();
	$ParentVBox->pack_start($MainVBox,0,0,0);
	
	# Create the table inside the frame
	my $ContentTable = Gtk2::Table->new(2,2);
	$ContentTable->show();
	$MainVBox->pack_start($ContentTable,1,1,0);
	
	# ==================================================================
	# ADD THE TIME/SUMMARY BOXES
	# ==================================================================
	
	# Create the time label
	my $TimeLabel = Gtk2::Label->new($Gettext->get('Time:'));
	$TimeLabel->show();
	$ContentTable->attach_defaults($TimeLabel, 0,1,0,1);
	
	# Time entry box
	my ($HourSpinner, $MinuteSpinner, $TimeHBox) = TimeSelection($EventTime);
	# Attach them to our main widget
	$ContentTable->attach_defaults($TimeHBox, 1,2,0,1);
	
	# Create the summary label
	my $SummaryLabel = Gtk2::Label->new($Gettext->get('Description:'));
	$SummaryLabel->show();
	$ContentTable->attach_defaults($SummaryLabel, 0,1,1,2);

	# Create the summary entry box
	my $SummaryEntry = Gtk2::Entry->new();
	if (defined($EventSummary) and length($EventSummary)) {
		$SummaryEntry->set_text($EventSummary);
	}
	$SummaryEntry->set_activates_default(1);
	$SummaryEntry->show();
	$ContentTable->attach_defaults($SummaryEntry, 1,2,1,2);
	$Tooltips->set_tip($SummaryEntry, $Gettext->get("Enter a description of the event here"));
	
	# ==================================================================
	# ADD THE EXTENDED ENTRY
	# ==================================================================
	my $FulltextWindow;
	
	my $FT_Expander = Gtk2::Expander->new($Gettext->get("Details"));
	$MainVBox->pack_start($FT_Expander,0,0,0);
	$FT_Expander->show();
	if ($UserConfig{EditorVerboseDefault}) {
		$FT_Expander->set_expanded(1);
	}
	$Tooltips->set_tip($FT_Expander, $Gettext->get("Use this if you need to enter additional information about the event"));
	
	# Add the extended entry box
	my $FulltextEntry = Gtk2::TextView->new();
	$FulltextEntry->set_editable(1);
	$FulltextEntry->set_wrap_mode("word-char");
	$FulltextEntry->show();
	# Add text to it if needed
	if (defined($EventFulltext) and $EventFulltext =~ /\S/) {
		my $AddEventBuffer = Gtk2::TextBuffer->new();
		$AddEventBuffer->set_text($EventFulltext);
		$FulltextEntry->set_buffer($AddEventBuffer);
		$FT_Expander->set_expanded(1);
	}

	$FulltextWindow = Gtk2::ScrolledWindow->new;
	$FulltextWindow->set_policy('automatic', 'automatic');
	$FulltextWindow->add($FulltextEntry);
	$FulltextWindow->show();
	$FT_Expander->add($FulltextWindow);
	
	return($MainVBox, $HourSpinner, $MinuteSpinner, $SummaryEntry, $FulltextEntry);
}

# Purpose: Create the widgets for editing a birthday(birthdays.dpd) event (%BirthdayContents)
# Usage: my ($BirthdayEventWidget, $SummaryWidget) = NormalEventWindow(SUMMARY, VBOX_WIDGET, MAIN_WINDOW_WIDGET);
#
# SUMMARY is the birthday you're editing or undef
# VBOX_WIDGET is the widget you want to pack our widgets into
# MAIN_WINDOW_WIDGET is the main window it will live inside
sub BirthdayEventWindow {
	# ==================================================================
	# INITIALIZE
	# ==================================================================
	my ($EventSummary, $ParentVBox, $MyWindow) = @_;

	my $IsEditing = 0;
	
	my ($EventYear, $EventMonth, $EventDay) = $CalendarWidget->get_date();$EventMonth++;
	my ($OldEventMonth,$OldEventDay,$OldEventSummary);
	
	# Check if the event is already defined, if it is then we assume that we're editing
	if(defined($EventSummary) and length($EventSummary)) {
		($OldEventMonth,$OldEventDay,$OldEventSummary) = ($EventMonth,$EventDay,$EventSummary);
		$IsEditing = 1;
	}
	
	# Create our main vbox
	my $MainVBox = Gtk2::VBox->new();
	$ParentVBox->pack_start($MainVBox,0,0,0);
	
	# Create the tooltips widget
	my $Tooltips = Gtk2::Tooltips->new();
	$Tooltips->enable();
	
	# Create the table inside the frame
	my $ContentTable = Gtk2::Table->new(2,2);
	$ContentTable->show();
	$MainVBox->pack_start($ContentTable,1,1,0);
	
	# ==================================================================
	# ADD THE TIME/SUMMARY BOXES
	# ==================================================================
	
	# Create the summary label
	my $SummaryLabel = Gtk2::Label->new($Gettext->get('Name:'));
	$SummaryLabel->show();
	$ContentTable->attach_defaults($SummaryLabel, 0,1,1,2);

	# Create the summary entry box
	my $SummaryEntry = Gtk2::Entry->new();
	if (defined($EventSummary) and length($EventSummary)) {
		$SummaryEntry->set_text($EventSummary);
	}
	$SummaryEntry->set_activates_default(1);
	$SummaryEntry->show();
	$ContentTable->attach_defaults($SummaryEntry, 1,2,1,2);
	$Tooltips->set_tip($SummaryEntry, $Gettext->get("Enter the name of the person whose birthday it is here"));
	
	return($MainVBox, $SummaryEntry);
}

# Purpose: Handle the OK button for the Normal event widget
# Usage: $OKButton->signal_connect('clicked' => sub { NormalEvent_OK($MainWindow, $HourSpinner, $MinuteSpinner, $SummaryEntry, $FulltextEntry, \%TimeHash, IS_EDITING)});
#
# IS_EDITING is either true or false, false when it's a new one being added, true when it's an
# old one being edited
sub NormalEvent_OK {
	my ($MyWindow, $HourSpinner, $MinuteSpinner, $SummaryEntry, $FulltextEntry, $TimeHash, $IsEditing) = @_;
	my $Error = 0;
	
	# Get the contents
	my $Time = GetTimeFromWidgets($HourSpinner,$MinuteSpinner);
	my $Summary = $SummaryEntry->get_text;
	my $FulltextBuff = $FulltextEntry->get_buffer;
	my $Fulltext = $FulltextBuff->get_text($FulltextBuff->get_bounds,1);

	# Get variables from the TimeHash
	my $EventYear = ${$TimeHash}{EventYear};
	my $EventMonth = ${$TimeHash}{EventMonth};
	my $EventDay = ${$TimeHash}{EventDay};
	my $OldEventYear = ${$TimeHash}{OldEventYear};
	my $OldEventMonth = ${$TimeHash}{OldEventMonth};
	my $OldEventDay = ${$TimeHash}{OldEventDay};
	my $OldEventTime = ${$TimeHash}{OldEventTime};
	
	# Make sure it's not a dupe (that would end up overwriting data)
	if ($IsEditing) {
		if (defined($CalendarContents{$EventYear}{$EventMonth}{$EventDay}{$Time})) {
			unless ($EventYear eq $OldEventYear and $EventMonth eq $OldEventMonth  and $EventDay eq $OldEventDay and $Time eq $OldEventTime) {
				DPError(sprintf($Gettext->get("There is already an event at %s on %s %s. Please select another time for this event."), $Time, $EventDay, $MonthNames{$EventMonth}));
				$Error = 1;
			}
		}
	} else {
		if (defined($CalendarContents{$EventYear}{$EventMonth}{$EventDay}{$Time})) {
				DPError(sprintf($Gettext->get("There is already an event at %s on %s %s. Please select another time for this event."), $Time, $EventDay, $MonthNames{$EventMonth}));
				$Error = 1;
			}
	}
	unless ($Summary) {
		DPError($Gettext->get("There is no summary for this event. Please enter a summary."));
		$Error = 1;
	}
	# We don't do this if an error occurred
	unless ($Error) {
		# Delete old hash if needed
		if($IsEditing) {
			CalendarDelete($OldEventYear,$OldEventMonth,$OldEventDay,$OldEventTime);
		}
		# Add them to the hash
		$CalendarContents{$EventYear}{$EventMonth}{$EventDay}{$Time}{'summary'} = $Summary;
		$CalendarContents{$EventYear}{$EventMonth}{$EventDay}{$Time}{'fulltext'} = $Fulltext;
		# Call UpdatedData to tell Day planner to save the data and redraw widgets
		UpdatedData();
		# Destroy our window, it will get the signal and do anything it needs to
		$MyWindow->destroy;
		$MainWindow->set_sensitive(1);
	}
}

# Purpose: Handle the OK button for the birthday event widget
# Usage: $OKButton->signal_connect('clicked' => sub { BirthdayEvent_OK ($MainWindow, $SummaryEntry, \%TimeHash, IS_EDITING)});
#
# IS_EDITING is either true or false, false when it's a new one being added, true when it's an
# old one being edited
sub BirthdayEvent_OK {
	my ($MyWindow, $SummaryEntry, $TimeHash, $IsEditing) = @_;
	my $Error = 0;
	
	# Get the contents
	my $Summary = $SummaryEntry->get_text;

	# Get variables from the TimeHash
	my $EventMonth = ${$TimeHash}{EventMonth};
	my $EventDay = ${$TimeHash}{EventDay};
	my $OldEventMonth = ${$TimeHash}{OldEventMonth};
	my $OldEventDay = ${$TimeHash}{OldEventDay};
	my $OldEventSummary = ${$TimeHash}{OldEventSummary};
	
	# Make sure it's not a dupe (that would end up overwriting data)
	if ($IsEditing) {
		if (defined($BirthdayContents{$EventMonth}{$EventDay}{$Summary})) {
			unless ($EventMonth eq $OldEventMonth  and $EventDay eq $OldEventDay and $Summary eq $OldEventSummary) {
				DPError($Gettext->get(sprintf("A Birthday already exists for \"%s\" on %s %s", $Summary, $EventDay, $MonthNames{$EventMonth})));
				$Error = 1;
			}
		}
	} else {
		if (defined($BirthdayContents{$EventMonth}{$EventDay}{$Summary})) {
			DPError($Gettext->get(sprintf("A Birthday already exists for \"%s\" on %s %s", $Summary, $EventDay, $MonthNames{$EventMonth})));
			$Error = 1;
		}
	}
	unless ($Summary) {
		DPError($Gettext->get("A name is required to add a birthday"));
		$Error = 1;
	}
	# We don't do this if an error occurred
	unless ($Error) {
		# Delete old hash if needed
		if($IsEditing) {
			BirthdayDelete($OldEventMonth,$OldEventDay,$OldEventSummary);
		}
		# Add them to the hash
		$BirthdayContents{$EventMonth}{$EventDay}{$Summary} = 1;
		# Call UpdatedData to tell Day planner to save the data and redraw widgets
		UpdatedData();
		# Destroy our window, it will get the signal and do anything it needs to
		$MyWindow->destroy;
		$MainWindow->set_sensitive(1);
	}
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# GUI functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# USER DIALOGS

# Purpose: Display the about dialog
# Usage: AboutBox();
sub AboutBox {
	$MainWindow->set_sensitive(0);
	my $AboutDialog = Gtk2::AboutDialog->new;
	$AboutDialog->set_transient_for($MainWindow);
	$AboutDialog->set_position('center-on-parent');
	$AboutDialog->set_authors("Eskild Hustvedt <zerodogg AT skolelinux DOT no>");
	$AboutDialog->set_artists("Jason Holland <jasonholland AT rawsoftware DOT com>");
	$AboutDialog->set_copyright("Copyright (C) Eskild Hustvedt 2006");
	$AboutDialog->set_website("http://home.gna.org/dayplanner/");
	$AboutDialog->set_name($Gettext->get("Day planner"));
	$AboutDialog->set_version($Version);
	# GPL summary, should never be marked as translateable
	$AboutDialog->set_license("Day planner is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nDay planner is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty\nof MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with day planner; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.");
	# Logo
	my $LogoImage = DetectImage("dayplanner-about.png","dayplanner_48.png","dayplanner_24.png", "dayplanner_16.png", "dayplanner_HC48.png","dayplanner_HC24.png", "dayplanner_HC16.png", "dayplanner.png");
	if ($LogoImage) {
		my $PixBuf = Gtk2::Gdk::Pixbuf->new_from_file($LogoImage);
		$AboutDialog->set_logo($PixBuf);
	}
	# Translator credits
	unless ($Gettext->get("THE NAMES OF THE TRANSLATORS") eq "THE NAMES OF THE TRANSLATORS") {
		$AboutDialog->set_translator_credits($Gettext->get("THE NAMES OF THE TRANSLATORS"));
	}
	$AboutDialog->run;
	$MainWindow->set_sensitive(1);
}

# Purpose: Draw the preferences window and allow the user to set different
#  configuration options
# Usage: PreferencesWindow();
sub PreferencesWindow {
	$MainWindow->set_sensitive(0);
	my $PreferencesWindow = Gtk2::Window->new();
	$PreferencesWindow->set_modal(1);
	$PreferencesWindow->set_transient_for($MainWindow);
	$PreferencesWindow->set_position('center-on-parent');
	$PreferencesWindow->set_title($Gettext->get("Preferences"));
	$PreferencesWindow->set_resizable(0);
	$PreferencesWindow->set_border_width(12);
	$PreferencesWindow->set_skip_taskbar_hint(1);
	$PreferencesWindow->set_skip_pager_hint(1);
	my $Tooltips = Gtk2::Tooltips->new();
	$Tooltips->enable();

	# Handle closing
	$PreferencesWindow->signal_connect("destroy" => sub { 
			$MainWindow->set_sensitive(1);
			SaveDefaultConfig();
			});
	
	# Creat the vbox
	my $Config_VBox = Gtk2::VBox->new();
	$Config_VBox->show();
	$PreferencesWindow->add($Config_VBox);
	
	# ==================================================================
	# GENERAL
	# ==================================================================
	
	# Create the primary vbox
	my $General_VBox = Gtk2::VBox->new();
	$General_VBox->show();
	$Config_VBox->pack_start($General_VBox,0,0,0);
	
	my $UI_Label = Gtk2::Label->new("<b>" . $Gettext->get("Interface") . "</b>");
	$UI_Label->set_use_markup(1);
	$UI_Label->set_alignment(0,1);
	$General_VBox->pack_start($UI_Label,0,0,0);
	$UI_Label->show();

	# EDITOR VERBOSE DEFAULT
	my $EDVDefaultCheckbox = Gtk2::CheckButton->new_with_label(sprintf($Gettext->get("Expand \"%s\" by default"), $Gettext->get("Details")));
	$General_VBox->pack_start($EDVDefaultCheckbox,0,0,0);
	$EDVDefaultCheckbox->show();
	if ($UserConfig{EditorVerboseDefault}) {
		$EDVDefaultCheckbox->set_active(1);
	}
	$EDVDefaultCheckbox->signal_connect( "toggled" => sub {
			if ($EDVDefaultCheckbox->get_active) {
				$UserConfig{EditorVerboseDefault} = 1;
			} else {
				$UserConfig{EditorVerboseDefault} = 0;
			}
		});
	
	$Tooltips->set_tip($EDVDefaultCheckbox, sprintf($Gettext->get("Check this if you want the \"%s\" to be expanded by default in the add and edit event dialog"), $Gettext->get("Details")));

	# ==================================================================
	# EVENTS
	# ==================================================================
	
	# A simple hash we use to parse back and forth between the PreNotification
	# combo box and the config option itself
	my %PreNotificationParser = (
		0 => '10min',
		1 => '20min',
		2 => '30min',
		3 => '45min',
		4 => '1hr',
		5 => '2hrs',
		6 => '4hrs',
		7 => '6hrs',
		
		'10min' => 0,
		'20min' => 1,
		'30min' => 2,
		'45min' => 3,
		'1hr' => 4,
		'1hrs' => 4,
		'2hr' => 5,
		'2hrs' => 5,
		'4hr' => 6,
		'4hrs' => 6,
		'6hr' => 7,
		'6hrs' => 7,

		'default' => 3,
		'readable_default' => '45mins',
	);
	
	# Create the vbox
	my $Events_VBox = Gtk2::VBox->new();
	$Events_VBox->show();
	$Config_VBox->pack_start($Events_VBox,0,0,0);

	# The label
	my $Events_Label = Gtk2::Label->new("<b>" . $Gettext->get("General events") . "</b>");
	$Events_Label->set_use_markup(1);
	$Events_Label->set_alignment(0,1);
	$Events_VBox->pack_start($Events_Label,0,0,0);
	$Events_Label->show();

	# If the reminder should autostart on login or not
	my $AutostartReminder_Checkbox = Gtk2::CheckButton->new_with_label($Gettext->get("Start the day planner reminder automatically when logging in"));
	$AutostartReminder_Checkbox->show();
	$Events_VBox->pack_start($AutostartReminder_Checkbox,0,0,0);
	$Tooltips->set_tip($AutostartReminder_Checkbox,$Gettext->get("If you want the day planner reminder to start automatically when you log in"));
	$AutostartReminder_Checkbox->set_active(1) if $InternalConfig{AddedAutostart};
	$AutostartReminder_Checkbox->signal_connect('toggled' => sub {
			if($AutostartReminder_Checkbox->get_active) {
				DP_AddAutostart();
			} else {
				unless(DP_RemoveAutostart()) {
					$AutostartReminder_Checkbox->set_active(1);
				}
			}
		});
	
	# The main checkbox that enables/disables the rest of the page
	my $Events_EnabledCheckbox = Gtk2::CheckButton->new_with_label($Gettext->get("Use notifications before events"));
	$Events_EnabledCheckbox->show();
	$Events_VBox->pack_start($Events_EnabledCheckbox,0,0,0);
	$Tooltips->set_tip($Events_EnabledCheckbox, $Gettext->get("Check this if you want day planner to notify you about events before they take place"));

	# The widgets that selects when the user wants to be notified
	# The HBox
	my $TimeSel_HBox = Gtk2::HBox->new();
	$Events_VBox->pack_start($TimeSel_HBox,0,0,0);
	$TimeSel_HBox->show();
	# The label
	my $TimeSel_Label = Gtk2::Label->new($Gettext->get('Time before the event to display the notification') . ': ');
	$TimeSel_Label->show();
	$TimeSel_HBox->pack_start($TimeSel_Label,0,0,0);
	# The combo box
	my $TimeSel_Combo = Gtk2::ComboBox->new_text();
	$TimeSel_Combo->insert_text(0, sprintf($Gettext->get("%s minutes"),"10"));
	$TimeSel_Combo->insert_text(1, sprintf($Gettext->get("%s minutes"),"20"));
	$TimeSel_Combo->insert_text(2, sprintf($Gettext->get("%s minutes"),"30"));
	$TimeSel_Combo->insert_text(3, sprintf($Gettext->get("%s minutes"),"45"));
	$TimeSel_Combo->insert_text(4, sprintf($Gettext->get("%s hour"),"1"));
	$TimeSel_Combo->insert_text(5, sprintf($Gettext->get("%s hours"),"2"));
	$TimeSel_Combo->insert_text(6, sprintf($Gettext->get("%s hours"),"4"));
	$TimeSel_Combo->insert_text(7, sprintf($Gettext->get("%s hours"),"6"));
	# Set the value
	if($UserConfig{Events_NotifyPre} eq "0") {
		$TimeSel_Combo->set_active(3);
	} elsif (defined($PreNotificationParser{$UserConfig{Events_NotifyPre}})) {
		$TimeSel_Combo->set_active($PreNotificationParser{$UserConfig{Events_NotifyPre}});
	} else {
		$TimeSel_Combo->set_active($PreNotificationParser{'default'});
	}
	$TimeSel_Combo->show();
	$TimeSel_HBox->pack_start($TimeSel_Combo,0,0,0);
	# Register the changed signal
	$TimeSel_Combo->signal_connect('changed' => sub {
				$UserConfig{Events_NotifyPre} = $PreNotificationParser{$TimeSel_Combo->get_active};
			});
	
	# If the user wants to be notified in advance
	my $NotifyAdvance_CheckBox = Gtk2::CheckButton->new_with_label($Gettext->get("Warn me one day in advance"));
	$NotifyAdvance_CheckBox->show();
	$Events_VBox->pack_start($NotifyAdvance_CheckBox,0,0,0);
	$NotifyAdvance_CheckBox->signal_connect('toggled' => sub {
			$UserConfig{Events_DayNotify} = $NotifyAdvance_CheckBox->get_active();
		});
	if($UserConfig{Events_DayNotify}) {
		$NotifyAdvance_CheckBox->set_active(1);
	}
	$Tooltips->set_tip($NotifyAdvance_CheckBox, $Gettext->get("Check this if you want day planner to warn you about events one day before they take place"));
	
	# This needs to be at the bottom so that we can sensitiveize or not the rest of the dialog
	$Events_EnabledCheckbox->signal_connect('toggled' => sub {
			if($Events_EnabledCheckbox->get_active) {
				$TimeSel_Combo->set_sensitive(1);
				$TimeSel_Label->set_sensitive(1);
				$NotifyAdvance_CheckBox->set_sensitive(1);
				$UserConfig{Events_NotifyPre} = $PreNotificationParser{$TimeSel_Combo->get_active};
			} else {
				$TimeSel_Combo->set_sensitive(0);
				$TimeSel_Label->set_sensitive(0);
				$NotifyAdvance_CheckBox->set_sensitive(0);
				$UserConfig{Events_NotifyPre} = 0;
			}
		});
	if($UserConfig{Events_NotifyPre}) {
		$Events_EnabledCheckbox->set_active(1);
	} else {
		$Events_EnabledCheckbox->set_active(0);
		$TimeSel_Combo->set_sensitive(0);
		$TimeSel_Label->set_sensitive(0);
		$NotifyAdvance_CheckBox->set_sensitive(0);
	}
	
	# ==================================================================
	# FINALIZE WINDOW
	# ==================================================================
	# Add the buttons
	my $ButtonHBox = Gtk2::HBox->new();
	$ButtonHBox->show();
	$Config_VBox->pack_start($ButtonHBox,0,0,0);

	my $CloseButton = Gtk2::Button->new_from_stock('gtk-close');
	$CloseButton->signal_connect("clicked" => sub { 
			$PreferencesWindow->destroy;
		});
	$CloseButton->show();
	$ButtonHBox->pack_end($CloseButton,0,0,0);

	# Show the config window
	$PreferencesWindow->show();
}

# MAIN WINDOW

# Purpose: Draw the eventlist on the currently selected date in the calendar
# Usage: DrawEventlist();
sub DrawEventlist {
	my $Year = $CalendarWidget->year;
	my $Month = $CalendarWidget->month;$Month++;
	my $Day = $CalendarWidget->selected_day;
	
	# New eventlist so make the toolbar edit button and edit/delete menu entries insensitive
	$ToolbarEditButton->set_sensitive(0);
	$MenuEditEntry->set_sensitive(0);
	$MenuDeleteEntry->set_sensitive(0);

	# Create the widget if needed
	unless ($EventlistWidget) {
		$EventlistWidget = Gtk2::SimpleList->new (
			$Gettext->get("Time") => 'text',
			$Gettext->get("Event") => 'text',
		);
		$EventlistWidget->set_rules_hint(1);
		$EventlistWidget->signal_connect("row_activated" => \&EditEvent);
		$EventlistWin->add($EventlistWidget);
		# Handle making the toolbar edit button sensitive
		$EventlistWidget->signal_connect('focus-in-event' => sub {
			my $Selected = [$EventlistWidget->get_selected_indices]->[0];
			# If there is something in that data array then there is something selected on focus-in-event
			if(@{$EventlistWidget->{data}}) {
				$ToolbarEditButton->set_sensitive(1);
				$MenuEditEntry->set_sensitive(1);
				$MenuDeleteEntry->set_sensitive(1);
			}
		});
	} else {
		# Remove data if needed
		@{$EventlistWidget->{data}} = ();
	}
	# Main calendar contents
	if (defined($CalendarContents{$Year}) and defined($CalendarContents{$Year}{$Month}) and defined($CalendarContents{$Year}{$Month}{$Day})) {
		foreach my $Time (sort keys %{$CalendarContents{$Year}{$Month}{$Day}}) {
			unless (defined($CalendarContents{$Year}{$Month}{$Day}{$Time}{"summary"})) {
				DPIntWarn("Found no summary for entry: $Time");
			} else {
				push (@{$EventlistWidget->{data}}, [$Time, $CalendarContents{$Year}{$Month}{$Day}{$Time}{"summary"}]);
			}
		}
	}
	if (defined($BirthdayContents{$Month}) and defined($BirthdayContents{$Month}{$Day})) {
		foreach my $Birthday (sort keys %{$BirthdayContents{$Month}{$Day}}) {
			push (@{$EventlistWidget->{data}}, ["", sprintf($Gettext->get("%s's birthday"), $Birthday)]);
		}
	}
	unless($Year > 2037) {
		unless($Year < 1970) {
			unless(defined($HolidayParser)) {
				$HolidayParser = Date::HolidayParser->new($HolidayFile);
			}
			unless(defined($Holidays{$Year})) {
					$Holidays{$Year} = $HolidayParser->get($Year);
			}
			if(defined($Holidays{$Year}) and defined($Holidays{$Year}->{$Month}) and defined($Holidays{$Year}->{$Month}{$Day})) {
				foreach my $CurrHoliday (keys(%{$Holidays{$Year}->{$Month}{$Day}})) {
					push(@{$EventlistWidget->{data}}, ["", $CurrHoliday]);
				}
			}
		}

	}

	$EventlistWidget->show();
}

# Purpose: Draw the main window
# Usage: DrawMainWindow();
sub DrawMainWindow {
	# ==================================================================
	# BUILD THE MAIN WINDOW
	# ==================================================================
	# Create the main window widget
	$MainWindow = Gtk2::Window->new('toplevel');
	$MainWindow->set_title($Gettext->get("Day planner"));
	$MainWindow->set_default_size ($InternalConfig{MainWin_Width},$InternalConfig{MainWin_Height});
	$MainWindow->maximize if ($InternalConfig{MainWin_Maximized});

	# Set the icon
	my $MainWindowIcon = DetectImage("dayplanner_48.png","dayplanner_24.png", "dayplanner_16.png", "dayplanner_HC48.png","dayplanner_HC24.png", "dayplanner_HC16.png", "dayplanner.png");
	if ($MainWindowIcon) {
		$MainWindow->set_default_icon_from_file($MainWindowIcon);
	}

	# Make it handle closing
	$MainWindow->signal_connect("destroy" => \&QuitSub);
	$MainWindow->signal_connect("delete-event" => \&QuitSub);

	# Handle saving maximized state
	$MainWindow->signal_connect(window_state_event => sub {
		my ($widget, $event) = @_;
		$InternalConfig{MainWin_Maximized} = ($event->new_window_state & 'maximized');
	});
		

	# Create the primary VBox for use inside it
	my $PrimaryWindowVBox = Gtk2::VBox->new();
	$PrimaryWindowVBox->show();
	$MainWindow->add($PrimaryWindowVBox);

	# ==================================================================
	# MENUBAR
	# ==================================================================
	# The menu items
	my @MenuItems = (
		# Calendar menu
		[ "/" . $Gettext->get("_Calendar"),						undef,			undef,			0,	"<Branch>"],
		[ "/" . $Gettext->get("_Calendar") . "/tearoff",				undef,			undef,			0,	"<Tearoff>"],
		[ "/" . $Gettext->get("_Calendar") . "/" . $Gettext->get("_Add an event"),		"<control>A",		\&AddEvent,		1,	'<StockItem>',	'gtk-add' ],
		[ "/" . $Gettext->get("_Calendar") . "/" . $Gettext->get("_Edit this event"),	"<control>E",		\&EditEvent,		2,	'<StockItem>',	'gtk-edit' ],
		[ "/" . $Gettext->get("_Calendar") . "/" . $Gettext->get("_Delete this event"),	"<control>D",		\&DeleteEvent,		3,	'<StockItem>',	'gtk-delete' ],
		[ "/" . $Gettext->get("_Calendar") . "/sep1",					undef,			undef,			4,	"<Separator>"],
		[ "/" . $Gettext->get("_Calendar") . "/" . $Gettext->get("_Preferences"),		undef,			\&PreferencesWindow,		5, 	'<StockItem>', 'gtk-preferences' ],
		[ "/" . $Gettext->get("_Calendar") . "/sep2",					undef,			undef,			6,	'<Separator>'],
		[ "/" . $Gettext->get("_Calendar") . "/" . $Gettext->get("_Quit"),		"<control>Q",		\&QuitSub,		0,	"<StockItem>",	'gtk-quit'],
		# Help menu
		[ "/" . $Gettext->get("_Help"),						undef,			undef,			0,	"<Branch>" ],
		[ "/" . $Gettext->get("_Help") . "/" . $Gettext->get("_About"),			undef,			\&AboutBox,		0,	'<StockItem>',	'gtk-about'],
	);
	# The accelgroup to use for the menuitems
	my $Menu_AccelGroup = Gtk2::AccelGroup->new;
	$MainWindow->add_accel_group($Menu_AccelGroup);
	# The item factory (menubar) itself
	my $Menu_ItemFactory = Gtk2::ItemFactory->new('Gtk2::MenuBar', '<main>', $Menu_AccelGroup);
	# Tell the item factory to use the items defined in @MenuItems
	$Menu_ItemFactory->create_items (undef, @MenuItems);
	# Pack it onto the vbox
	$PrimaryWindowVBox->pack_start($Menu_ItemFactory->get_widget("<main>"), 0, 0, 0);
	# Show it
	$Menu_ItemFactory->get_widget("<main>")->show();
	
	# Create two widget objects for the edit/delete menu entries
	my $Get = "/". $Gettext->get("_Calendar") . "/" . $Gettext->get("_Edit this event");
	$Get =~ s/_//g;
	$MenuEditEntry = $Menu_ItemFactory->get_widget($Get);

	$Get = "/" . $Gettext->get("_Calendar") . "/" . $Gettext->get("_Delete this event");
	$Get =~ s/_//g;
	$MenuDeleteEntry = $Menu_ItemFactory->get_widget($Get);

	# ==================================================================
	# TOOLBAR
	# ==================================================================
	$Toolbar = Gtk2::Toolbar->new();
	$PrimaryWindowVBox->pack_start($Toolbar,0,0,0);
	$Toolbar->show();

	# Edit button
	$ToolbarEditButton = Gtk2::ToolButton->new_from_stock('gtk-edit');
	$ToolbarEditButton->signal_connect('clicked' => \&EditEvent);
	$Toolbar->insert($ToolbarEditButton,0);
	$ToolbarEditButton->show();
	
	# Add button
	my $AddButton = Gtk2::ToolButton->new_from_stock('gtk-add');
	$AddButton->signal_connect("clicked" => \&AddEvent);
	$Toolbar->insert($AddButton,0);
	$AddButton->show();

	$Toolbar->get_nth_item(0)->set_is_important(1);
	$Toolbar->get_nth_item(1)->set_is_important(1);

	# ==================================================================
	# WORKING AREA
	# ==================================================================
	# Create the hbox which will contain the rest of the program
	$WorkingAreaHBox = Gtk2::HBox->new();
	$WorkingAreaHBox->show();
	# Add it to the primary VBox
	$PrimaryWindowVBox->pack_start($WorkingAreaHBox,1,1,0);
	
	# ==================================================================
	# THE RIGHT HAND AREA
	# ==================================================================
	
	# Create the vbox for use in it
	my $RightHandVBox = Gtk2::VBox->new();
	$WorkingAreaHBox->pack_end($RightHandVBox,0,0,0);
	$RightHandVBox->show();

	# CALENDAR
	# Create the calendar
	$CalendarWidget = Gtk2::Calendar->new;
	GetDate();
	SetActiveCalItems($curryear, $currmonth);
	$CalendarWidget->show();
	$CalendarWidget->display_options(['show-week-numbers', 'show-day-names','show-heading']);
	$currmonth--;
	# Work around a possible Gtk2::Calendar bug by explicitly setting the month/year combo
	$CalendarWidget->select_month($currmonth, $curryear);
	$RightHandVBox->pack_start($CalendarWidget,0,0,0);
	
	my $LastDay = Get_DateInfo($CalendarWidget);

	$CalendarWidget->signal_connect('prev-month' => \&CalendarChange);
	$CalendarWidget->signal_connect('next-month' => \&CalendarChange);
	$CalendarWidget->signal_connect('prev-year' => \&CalendarChange);
	$CalendarWidget->signal_connect('next-year' => \&CalendarChange);
	$CalendarWidget->signal_connect('day-selected' => sub {
			if(Get_DateInfo($CalendarWidget) eq $LastDay) {
				return;
			}
			$LastDay = Get_DateInfo($CalendarWidget);
			DrawEventlist();
		});

	# UPCOMING EVENTS
	# Create the scrolled window
	my $UpcomingEventsWindow = Gtk2::ScrolledWindow->new;
	$UpcomingEventsWindow->set_policy('automatic', 'automatic');
	$UpcomingEventsWindow->show();
	# Create the TextView and TextBuffer objects
	$UpcomingEventsWidget = Gtk2::TextView->new();
	$UpcomingEventsBuffer = Gtk2::TextBuffer->new();
	$UpcomingEventsWidget->set_buffer($UpcomingEventsBuffer);
	$UpcomingEventsWidget->set_editable(0);
	$UpcomingEventsWidget->set_wrap_mode("word");
	$UpcomingEventsWidget->show();
	$UpcomingEventsWindow->add($UpcomingEventsWidget);
	# Pack it onto the main window
	$RightHandVBox->pack_end($UpcomingEventsWindow,1,1,0);
	
	# ==================================================================
	# TASK LIST
	# ==================================================================
	
	# Add a window for use for it
	$EventlistWin = Gtk2::ScrolledWindow->new;
	$EventlistWin->set_policy('automatic', 'automatic');
	$WorkingAreaHBox->pack_start($EventlistWin,1,1,0);
	$EventlistWin->show();
	
	# Draw the initial eventlist
	DrawEventlist();
	
	# ==================================================================
	# FINALIZE AND DISPLAY
	# ==================================================================

	# Finally, show the window and rest in the main Gtk2 loop
	$MainWindow->show();
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Core functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Get commandline options
GetOptions (
	'help|h' => sub {
		print "Day planner version $Version\n\n";
		PrintHelp("-h","--help","Display this help screen and exit");
		PrintHelp("-v","--version", "Display version information and exit");
		PrintHelp("-t","--test","Use a seperate debug/ configuration directory");
		PrintHelp("", "--confdir", "Use the directory supplied as configuration directory");
		PrintHelp("","--debuginfo", "Display information useful for debugging and exit");
		exit(0);
	},
	'version|v' => sub {
		print "Day planner version $Version\n";
		print "RCS revision: $RCSRev\n";
		exit(0);
	},
	'debuginfo' => sub {
		print "Day planner version $Version\n";
		print "RCS revision: $RCSRev\n";
		print "Gtk2 version ", join (".", Gtk2->GET_VERSION_INFO),"\n";
		printf "Perl version %vd\n", $^V;
		print "OS: ", GetDistVer(), "\n";
		exit(0);
	},
	'test|t' => sub {
		print "*** (Day Planner $Version) Running in test mode\n";
		$SaveToDir = "$ENV{HOME}/.dayplanner/debug";
	},
	'confdir=s' => sub {
		if (-e $_[1]) {
			unless (-d $_[1]) {
				die "$_[1] is not a directory\n";
			}
			unless (-w $_[1]) {
				die "$_[1] is not writeable\n";
			}
		}
		$SaveToDir = $_[1];
	},
) or die "Run $0 --help for more information\n";

my $Gtk2_Initialized = 0;

# If for some obscure reason HOME isn't set we can't continue
unless(defined($ENV{HOME}) and $ENV{HOME}) {
	Gtk2->init;
	DPError(sprintf($Gettext->get("The environment variable %s is not set! Unable to continue\n"), "HOME"));
	die(sprintf($Gettext->get("The environment variable %s is not set! Unable to continue\n"), "HOME"));
}
# The same goes for if HOME doesn't exist or isn't a directory
unless(-e $ENV{HOME} and -d $ENV{HOME}) {
	printf($Gettext->get("Your home directory (%s) doesn't exist! Please verify that the environment variable %s is properly set. Unable to continue\n"), $ENV{HOME}, "HOME");
	Gtk2->init;
	DPError(sprintf($Gettext->get("Your home directory (%s) doesn't exist! Please verify that the environment variable %s is properly set. Unable to continue\n"), $ENV{HOME}, "HOME"));
	die("\n");
}
Gtk2->init;
# Set SaveToDir and HolidayFile
$SaveToDir = "$ENV{HOME}/.dayplanner" unless(defined($SaveToDir));	# The configuration and eventlist directory
$HolidayFile = "$SaveToDir/holidays";					# The file to load holiday definitions from

# Load the configuration file
LoadDefaultConfig();
# Load the internal state file
LoadStateFile($SaveToDir, "state.conf");
# Load the calendar
LoadCalendar();
# Set up holidays
HolidaySetup();
# Draw the main window
DrawMainWindow();
Gtk2->main_iteration while Gtk2->events_pending;
# Initialize the daemon
DaemonInit() or die();
# Add autostart if needed
unless(defined($InternalConfig{AddedAutostart}) and $InternalConfig{AddedAutostart} eq "1") {
	DP_AddAutostart();
	$InternalConfig{AddedAutostart} = 1;
}
# Set the redraw timer and populate the upcoming events widget
PopulateUpcomingEvents();
Set_DayChangeTimer();

# Rest in the Gtk2 main loop
Gtk2->main;
