#!/usr/bin/perl
# Day Planner
# A graphical Day Planner written in perl that uses Gtk2
# Copyright (C) Eskild Hustvedt 2006, 2007
# $Id: dayplanner 1224 2007-03-25 11:01:14Z 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.

require 5.6.0;			# We need perl 5.6.0 at the very least
				# and even this is mostly untested.
use strict;			# Force strict coding
use warnings;			# Tell perl to warn about things
use POSIX;			# We need strftime
use Gtk2; 			# Use Gtk2 :)
use Gtk2::SimpleList;		# We use Gtk2::SimpleList to create the eventlist
use Gtk2::Gdk::Keysyms;		# Easier keybindings
use File::Basename;             # We meed dirname to help Cwd::Realpath finding the directory
use File::Copy;			# We need to copy the holiday file
use File::Path qw/mkpath/;	# We need mkpath
use Digest::MD5 qw(md5_base64 md5_hex);	# Used for import/export and .holiday upgrading
use MIME::Base64;		# Used for import/export
use Getopt::Long;		# Commandline options
use IO::Socket;			# Network layer to communicate with the daemon
use FindBin;			# So that we can detect module dirs during runtime
use Cwd;			# We need Cwd::Realpath to find out which directory we live in
# So that we can use a local Date::HolidayParser
use lib "$FindBin::RealBin/modules/";
use lib "$FindBin::RealBin/modules/Date-HolidayParser/lib/";
use lib "$FindBin::RealBin/modules/DP-iCalendar/lib/";
use Date::HolidayParser;	# Parsing of .holiday files
use DP::iCalendar qw(iCal_ParseDateTime iCal_GenDateTime);# iCalendar support

# Scalars
my $Version = "0.5.1";
my $RCSRev = '$Id: dayplanner 1224 2007-03-25 11:01:14Z 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 $ICSFile = "calendar.ics";				# The ICS calendar
my $ConfigFile = "dayplanner.conf";			# The filename to save the configuration to
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
my $DP_I18N_Mode = 0;
my $Gettext;
my $DPS_APILevel = "05A";				# The DPS API level used/supported
my $HTML_PHP = 0;					# If set to 1 the HTML exporter will not
							#  output the header/footer. This is used by the
							#  PHP exporter.
my $iCalendar;						# The DP::iCalendar object.



# 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 %Holidays;			# The holidays
my %InternalConfig;		# Internal configuration values
my %UserConfig;			# User-selected configuration values
my %DPServices;			# Day Planner services hash
my %MonthNames;			# The names of the months.

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

my $HolidayParser;	# The Date::HolidayParser object
my $Gtk2Init;		# Info about if gtk2 is initialized or not
my $ShutdownDaemon;	# If we should shut down the daemon on exit
my($AM_String, $PM_String, $ClockSystem);	# Time/date info

# 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;

# =============================================================================
# CORE FUNCTIONS
# =============================================================================

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# I18n functions (Day Planner Locale::gettext wrapper)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Wrapper around the gettext functions that does the right thing(tm)
# Usage: DP_gettext(STANDARD_GETTEXT_SYNTAX);
sub DP_gettext {
	if($DP_I18N_Mode == 1) {	# 1 is Legacy
		return(gettext(@_));
	} elsif ($DP_I18N_Mode == 2 ) {	# 2 is new Locale::gettext OO-interface
		return($Gettext->get(@_));
	} else {			# Neither of those means it's not available, just return
					# a scalar of the supplied data.
		my $String;
		foreach(@_) {
			$String .= $_;
		}
		return($String);
	}
}

# Purpose: Initialize the i18n subsystem
# Usage: DP_InitI18n();
sub DP_InitI18n {
	setlocale(LC_ALL, "" );
	if(eval("use Locale::gettext;1")) {
		my $BindTo;
		my $Legacy;
		if($Locale::gettext::VERSION > "1.04") {
			$Legacy = 0;
		} else {
			$Legacy = 1;
		}
		if(defined($ENV{DP_FORCE_LEGACY_I18N}) and $ENV{DP_FORCE_LEGACY_I18N} eq "1") {
			$Legacy = 1;
		}
		# 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") {
					$BindTo = "$FindBin::RealBin/locale";
				}
			}
		}
		# Set up the i18n fetching type
		if($Legacy) {
			DPIntWarn("Using legacy Locale::gettext. This will work, but is not officially supported and you may have some issues with certain accented characters");
			$DP_I18N_Mode = 1;
			bindtextdomain("dayplanner", "$FindBin::RealBin/locale");
			textdomain("dayplanner");
			# This is a hack, but works in many cases
			foreach my $EnvVar(qw(LANG LC_CTYPE LC_ALL LC_ADDRESS LC_NAME)) {
				bind_textdomain_codeset("dayplanner","ISO-8859-15");
			}
		} else {
			$DP_I18N_Mode = 2;
			$Gettext = Locale::gettext->domain("dayplanner");	# Set the gettext domain
			if($BindTo) {
				$Gettext->dir($BindTo);
			}
		}
	} else {
		$DP_I18N_Mode = 0;
		# No Locale::Gettext available
		DPIntWarn("Locale::gettext is not available. This will work, but localization will *not* be available");
	}
	# 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
	%MonthNames = (
		1 => DP_gettext("January"),
		2 => DP_gettext("February"),
		3 => DP_gettext("March"),
		4 => DP_gettext("April"),
		5 => DP_gettext("May"),
		6 => DP_gettext("June"),
		7 => DP_gettext("July"),
		8 => DP_gettext("August"),
		9 => DP_gettext("September"),
		10 => DP_gettext("October"),
		11 => DP_gettext("November"),
		12 => DP_gettext("December")
	);	# Localized hash of month number => Name values

	# AM/PM setup.
	# The %% in the end is *required*. It works around bugs in recent versions
	# of strftime()
	$AM_String = strftime('%p%%', 0,0,0,0,0,106,0);
	$PM_String = strftime('%p%%', 0,0,12,0,0,106,0);
	$AM_String =~ s/%p?%?$//;	# Remove junk
	$PM_String =~ s/%p?%?$//;	# Remove junk
	$ClockSystem = $AM_String eq "" ? "24" : "12";
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Daemon communication 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;
			my $DaemonVersion = Daemon_DataSegment("VERSION");
			chomp($DaemonVersion);
			unless($DaemonVersion eq $Version) {
				if(Daemon_DataSegment("SHUTDOWN") eq "okay\n") {
					$DaemonOnline = 0;
					return(StartDaemon());
				} else {
					DPIntWarn("Wanted to restart the daemon but was unable to!");
				}
			}
			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");
			if(not Daemon_AttemptReconnect()) {
				       DPIntWarn("Unable to reconnect, expect trouble");
				       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 {
	my $ConnectionReply = ConnectToDaemon("$SaveToDir/$DaemonSocketName");
	if($ConnectionReply eq 'NOCONNECT') {
		DPIntWarn("Unable to reconnect to the daemon, attempting to restart it");
		if(StartDaemon()) {
			DPIntInfo("Daemon started");
		} else {
			$DaemonOnline = 0;
			return(undef);
		}
	}
	if($ConnectionReply 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 (shutdown if $ShutdownDaemon);
# 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 {
			if($ShutdownDaemon) {
				my $GetDaemon = Daemon_DataSegment('SHUTDOWN');
				chomp($GetDaemon);
				unless($GetDaemon eq 'okay') {
					DPIntWarn("The daemon replied \"$GetDaemon\" to my SHUTDOWN request!");
				}
			} 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("Recieved 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 {
	if($Gtk2Init) {
		my $ErrorDialog = Gtk2::MessageDialog->new (undef,
						'destroy-with-parent',
						'error',
						'none',
						 DP_gettext("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(
			DP_gettext('_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(DP_gettext("Unable to force the start. This is likely due to a bug in the application, please contact the Day Planner developers."));
					exit(1);
				}
		}
	} else {
		die("Another instance of Day Planner appears to be running, unable to continue\n");
	}
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Services layer
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Return better errors than IO::Socket::INET does.
# Usage: my $ERROR = IO_Socket_INET_Errors($@);
# 	Errors:
#		OFFLINE = Network is unreachable
#		REFUSED = Connection refused
#		BADHOST = Bad hostname (should often be handled as OFFLINE)
#		* = Anything else simply returns $@
sub IO_Socket_INET_Errors {
	my $Error = shift;
	# TODO: There are probably quite a few unhandled errors. At least timeout.
	if($Error =~ /Network is unreachable/) {
		return("OFFLINE");
	} elsif ($Error =~ /Bad hostname/) {
		return("BADHOST");
	} elsif ($Error =~ /Connection refused/) {
		return("REFUSED");
	} else {
		return($Error);
	}
}

# Purpose: Back up data to ~/.dayplanner/ before getting data
# Usage: DPS_BackupData();
sub DPS_BackupData {
	my ($currsec,$currmin,$currhour,$currmday,$currmonth,$curryear,$currwday,$curryday,$currisdst) = localtime(time);
	my $Target = "$SaveToDir/dps_backup-$curryear.$currmonth.$currmday-$currhour$currmin$currsec.ics";
	$iCalendar->write($Target);
	DPS_Log("Backup written to $Target");
	DPIntInfo("DPS: Backup written to $Target");
	return($Target);
}

# Purpose: Connect to a Day Planner services server.
# Usage: my $Return = DPS_Connect(HOST, PORT, USER, PASS);
# 		The arguments are optional and will be read from %DPServices if not supplied
sub DPS_Connect {
	my ($Host, $Port, $User, $Password) = @_;
	$Host = $Host ? $Host : $DPServices{host};
	$Port = $Port ? $Port : $DPServices{port};
	$User = $User ? $User : $DPServices{user};
	$Password = $Password ? $Password : $DPServices{pass};
	my $Error;
	# Connect
	DPS_Status(DP_gettext("Connecting"),0);
	my $OLDLocale = setlocale(LC_ALL, "C");		# Need errors that are not localized for IO_Socket_INET_Errors();
	$DPServices{socket} = IO::Socket::INET->new(
					PeerAddr => $Host,
					PeerPort => $Port,
					Timeout => 30,
			) or do { $Error = $@; };
			
	setlocale(LC_ALL, ""); #, $OLDLocale);	# Reset the locale
	# Process errors if any occurred
	if($Error) {
		# If we have already displayed an error to the user this session, don't do it again
		if(defined($DPServices{Offline}) and $DPServices{Offline} eq 1) {
			DPS_Error(undef, "Unable to connect to $Host on port $Port: $@");
			return(undef);
		}

		$Error = IO_Socket_INET_Errors($Error);	# Get errors that are easier to process

			# Process network unreachable and bad hostname
		if($Error eq "OFFLINE" or $Error eq "BADHOST") {
			$DPServices{Offline} = 1;
			DPS_Error(sprintf(DP_gettext("Unable to connect to the Day Planner services server (%s)."), "$Host:$Port",) . " " . DP_gettext("You're probably not connected to the internet"), "Unable to connect to $Host on port $Port: $@ ($Error)");
		}
			# Process connection refused
		elsif($Error eq "REFUSED") {
			DPS_Error(sprintf(DP_gettext("Unable to connect to the Day Planner services server (%s)."), "$Host:$Port") . " " . DP_gettext("The connection was refused by the server. Please verify your Day Planner services settings.") . "\n\n" . DP_gettext("If this problem persists, please contact your service provider"), "Unable to connect to $Host on port $Port: $@ ($Error)");
		} 
			# Process unknown errors
		else {
			DPS_Error(sprintf(DP_gettext("Unable to connect to the Day Planner services server (%s)."), "$Host:$Port") . " " . DP_gettext("If this problem persists, please contact your service provider"), "Unable to connect to $Host on port $Port: $@");
		}
		return(undef);
	}

	# The connection didn't fail, so delete $DPServices{Offline} if it exists
	delete($DPServices{Offline});

	# Authenticate
	my $APIREPLY = DPS_DataSegment("APILEVEL $DPS_APILevel");
	return(undef) if DPS_ErrorIfNeeded("OK", $APIREPLY, sub { DPS_Disconnect();  DPS_Error(sprintf(DP_gettext("The Day Planner services server you are connecting to does not support this version of Day Planner (%s)."), $Version), "API error received from the server (my APILEVEL is $DPS_APILevel).");});
	DPS_Status(DP_gettext("Connecting"),0.2);
	my $AUTHREPLY = DPS_DataSegment("AUTH $User $Password");
	DPS_Status(DP_gettext("Connecting"),0.4);
	return(undef) if DPS_ErrorIfNeeded("OK", $AUTHREPLY, sub { DPS_Disconnect(); DPS_Error(DP_gettext("The username and/or password is incorrect."),"Authentication error");});
	return("OK");
}

# Purpose: Do something when an error has occurred
# Usage: my $Return = DPS_ErrorIfNeeded(EXPECTED_REPLY, RECIEVED_REPLY, CODEREF);
# 	The CODEREF will be run if EXPECTED_REPLY does not eq RECIEVED_REPLY
sub DPS_ErrorIfNeeded {
	my ($Expected, $Recieved, $ErrorSub) = @_;
	unless($Expected eq $Recieved) {
		$ErrorSub->($Recieved);
		return(1);
	} else {
		return(0);
	}
}

# Purpose: Disconnect from a Day Planner services daemon
# Usage: DPS_Disconnect();
sub DPS_Disconnect {
	my $Socket = $DPServices{socket};
	close($Socket);
	delete($DPServices{socket});
	return(1);
}

# Purpose: Send data to a Day Planner services daemon and get the reply
# Usage: my $Reply = DPS_DataSegment(DATA_TO_SEND);
sub DPS_DataSegment {
	my $Socket = $DPServices{socket};
	print $Socket "$_[0]\n";
	my $Data = <$Socket>;
	unless(defined($Data)) {
		$Data = '';
	}
	chomp($Data);
	return($Data);
}

# Purpose: Repair a checksum mismatch
# Usage: DPS_RepairData();
# 	returns true on success, false on failure
sub DPS_RepairData {
	my $ProgressWin = DPCreateProgressWin(DP_gettext("Repairing"),DP_gettext("Getting data"),0);
	my $RawData = DPS_Perform("GET_RAW");
	if($RawData eq "MATCHED") {
		$ProgressWin->{Window}->destroy();
		return(1);
	}
	if(defined($RawData)) {
		$ProgressWin->{ProgressBar}->set_fraction(0.4);
		$ProgressWin->{ProgressBar}->set_text(DP_gettext("Parsing and repairing"));
		my @DataArray;
		push(@DataArray, $_) foreach(split(/\r\n/,$RawData));
		$RawData = undef;
		$InternalConfig{DPS_LastMD5} = DPS_Perform("GET_MD5");
		$iCalendar->enable("SMART_MERGE");	# SMART_MERGE is required to ensure no data is lost
		$iCalendar->addfile(\@DataArray);
		$iCalendar->disable("SMART_MERGE");	# but we don't want to use it by default
		UpdatedData();
		$ProgressWin->{ProgressBar}->set_fraction(1);
		$ProgressWin->{Window}->destroy();
		return(1);
	} else {
		$ProgressWin->{Window}->destroy();
		my $BackedUpTo = DPS_BackupData();
		DPS_Error(sprintf(DP_gettext("Unable to download the raw Day Planner data from the server. Repairing is not possible! Backup written to %s."), $BackedUpTo), "Unable to retrieve the raw data - repairing is impossible. Backup written to $BackedUpTo.");
		return(0);
	}
}

# Purpose: Get Day Planner data from a services server
# Usage: DPS_GetData(RAW?);
sub DPS_GetData {
	my $Raw = $_[0] ? 1 : 0;
	DPS_Log("DPS_GetData($Raw) called");
	if(defined($InternalConfig{DPS_ParanoidBackup}) and $InternalConfig{DPS_ParanoidBackup} eq "1") {
		DPS_BackupData();
	}
	my $MainData;
	DPS_Status(DP_gettext("Verifying data"), 0.3);
	if(not $DPServices{GET_MD5_Checked}) {
		$DPServices{GET_MD5_Checked} = 1;
		$DPServices{StaticUID} = 1;
		my $Current_DataMD5 = encode_base64($iCalendar->get_rawdata(),"");
		chomp($Current_DataMD5);
		$Current_DataMD5 = md5_base64($Current_DataMD5);
		$DPServices{StaticUID} = 0;
		if(defined($InternalConfig{DPS_LastMD5})) {
			unless($Current_DataMD5 eq $InternalConfig{DPS_LastMD5}) {
				DPS_Log("$Current_DataMD5 (current) did not match DPS_LastMD5 $InternalConfig{DPS_LastMD5}. Forcing data repairing");
				# NOTE: Forcing this repair here is "kind of" dangerous. It might result
				# in deleted events reappearing. This will be very very very rare however,
				# currently it has been judged as the best way to fix this.
				return(DPS_RepairData());
			}
		}
	}
	return unless DPS_Connect();
	DPS_Status(DP_gettext("Downloading data"),0.5);
	my $ServerMD5 = DPS_DataSegment("GET_MD5");
	# If the MD5 on the server is identical to ours we don't need to download the data
	if($ServerMD5 =~ /^(\[NONE\]|ERR)/ or (defined($InternalConfig{DPS_LastMD5}) and $ServerMD5 eq $InternalConfig{DPS_LastMD5})) {
		DPS_Log("Skipping download, the data on the server matched our data ($InternalConfig{DPS_LastMD5} = $ServerMD5)");
		DPS_Disconnect();
		DPS_Status(DP_gettext("Done"), 1);
		return("MATCHED");
	}
	if(defined($InternalConfig{DPS_LastMD5})) {
		DPS_Log("The server had $ServerMD5, I had $InternalConfig{DPS_LastMD5} - downloading data");
	} else {
		DPS_Log("The server had $ServerMD5, I had no MD5 - downloading data");
	}
	my $Data = DPS_DataSegment("GETDATA");
	if($Data =~ /^OK/) {
		DPS_Log("Got data");
		my $Initial = $Data;
		my $MD5 = $Data;
		$MainData = $Data;
		$Initial =~ s/^(\S+)\s+.*$/$1/;
		$MD5 =~ s/^(\S+)\s+(\S+)\s+.*/$2/;
		if(not $MainData =~ s/^(\S+)\s+(\S+)\s+(\S+)\s*$/$3/) {
			DPS_Error("FATAL: UNABLE TO GRAB DATA. DUMPING DATA:\nData recieved: $Initial");
		}
		unless(md5_base64($MainData) eq $MD5) {
			DPS_Error(DP_gettext("The data corrupted itself during download. You may want to attempt to synchronize again."),"MD5 mismatch during download: got " . md5_base64($MainData) . " expected $MD5");
		} else {
			$MainData = decode_base64($MainData);
			unless($Raw) {
				my @DataArray;
				$MainData =~ s/\r//g;
				push(@DataArray, $_) foreach(split(/\n/,$MainData));
				$MainData = undef;
				$iCalendar->clean();
				$iCalendar->addfile(\@DataArray);
				UpdatedData();
			}
		}
	} else {
		if($Data =~ /^ERR NO_DATA/) {
			# No data found on the server, this is fine.
			DPS_Log("No data found on the server.");
		} elsif($Data =~ /^ERR MD5_FAILURE/) {
			DPS_Error(DP_gettext("The data is corrupt on the services server. The administrator has been notified about this. If this problem does not go away within a few days, please contact your services provider"), "MD5 failure on server");
		} else {
			DPS_Error("An unknown error occurred (the server replied $Data)", "Unknown error: $Data");
		}
	}
	DPS_Disconnect();
	DPS_Status(DP_gettext("Downloaded data"),1);
	if($Raw) {
		if (defined($MainData) and length($MainData)) {
			return($MainData);
		} else {
			return(undef);
		}
	} else {
		return(1);
	}
}

# Purpose: Send data to a services server
# Usage: DPS_SendData();
sub DPS_SendData {
	my $Force = $_[0] ? 1 : 0;
	DPS_Log("DPS_SendData($Force) called");
	return if not DPS_Connect();
	DPS_Status(DP_gettext("Sending data"),0.5);
	# Force UIDs in the iCalendar file to be static (less random)
	$DPServices{StaticUID} = 1;
	my $SendData = encode_base64($iCalendar->get_rawdata(),"");
	$DPServices{StaticUID} = 0;
	chomp($SendData);
	my $MD5 = md5_base64($SendData);
	my $ServerMD5 = DPS_DataSegment("GET_MD5");
	# Don't send the data if the checksums match
	if (not $ServerMD5 =~ /^(\[NONE\]|ERR)/ and $ServerMD5 eq $MD5) {
		DPS_Log("Not sending data - checksums matched");
		DPS_Disconnect();
	} else {
		DPS_Log("Sending data to server. My MD5 is $MD5, the one on the server is $ServerMD5.");
		if (defined($InternalConfig{DPS_LastMD5}) and length($InternalConfig{DPS_LastMD5})) {
		       	# Fail with an error if the checksums of $ServerMD5 and $InternalConfig{DPS_LastMD5} doesn't match
			if (not $ServerMD5 =~ /^(\[NONE\]|ERR)/ and not $ServerMD5 eq $InternalConfig{DPS_LastMD5}) {
				DPS_Disconnect();
				DPIntWarn("ServerMD5[$ServerMD5] does not match DPS_LastMD5[$InternalConfig{DPS_LastMD5}] current MD5[$MD5]. Repair required for successful upload.");
				DPS_Log("ServerMD5[$ServerMD5] does not match DPS_LastMD5[$InternalConfig{DPS_LastMD5}] current MD5[$MD5]. Repair required for successful upload.");
				return(0) unless DPS_RepairData();
				return(0) unless DPS_Connect();
			}
		}
		my $LastMD5 = $InternalConfig{DPS_LastMD5} ? $InternalConfig{DPS_LastMD5} : "undef";
		my $Reply = DPS_DataSegment("SENDDATA $MD5 $LastMD5 $SendData $Force");
		$InternalConfig{DPS_LastMD5} = $MD5;
		DPS_Disconnect();
		unless($Reply eq "OK") {
			# TODO: These need cleaning
			if($Reply =~ s/^ERR\s+(.*)$/$1/) {
				DPS_Error(DP_gettext("An error ocurred during uploading of the data"), "An error ocurred during upload of the data: $Reply");
			} elsif($Reply =~ /^EXPIRED/) {
				DPS_Error(DP_gettext("Your account has expired. If you are using a payed service this may be because you have not payed for the current period. If not, you should contact your service provider to get information on why your account has expired."), "Account expired");
			} else {
				# Sending the data failed
				DPS_Error(sprintf(DP_gettext("The server did not accept the uploaded data and replied with an unknwown value: %s"), $Reply, "The server did not accept the uploaded data: $Reply"));
			}
			return(undef);
		}
	}
	DPS_Status(DP_gettext("Data sent"),1);
	return(1);
}

# Purpose: Output an error occurring with DPS
# Usage: DPS_Error(User_Error, Technical_Error)
# 	User_Error is displayed as a pop-up error dialog.
# 	Technical_Error is DPIntWarn()ed
sub DPS_Error {
	my ($user_error, $tech_error) = @_;
	DPIntWarn("DPS: $tech_error");
	DPS_Log($tech_error);
	if(defined($user_error)) {
		$DPServices{Error} = $user_error;
	}
}

# Purpose: Wrapper around DPS functions setting stuff up correctly
# Usage: DPS_Perform(FUNCTION)
# 	Function can be one of:
# 	GET	- Get data from the server
# 	SEND	- Send data to the server
sub DPS_Perform {
	my $Function = $_[0];
	return unless(defined($InternalConfig{DPS_Enable}) and $InternalConfig{DPS_Enable} eq "1");
	return if(defined($ENV{DP_DISABLE_SERVICES}) and $ENV{DP_DISABLE_SERVICES} eq "1");
	# Verify that options are set
	foreach my $Option (qw(host port user pass)) {
		unless(defined($InternalConfig{"DPS_$Option"}) and length($InternalConfig{"DPS_$Option"})) {
			DPIntWarn("DPS enabled but the setting DPS_$Option is missing. DPS will not be used.");
			return(undef);
		} else {
			$DPServices{$Option} = $InternalConfig{"DPS_$Option"};
		}
	}
	if(not defined($DPServices{Log_FH})) {
		open($DPServices{Log_FH}, ">", "$SaveToDir/services.log");
		DPS_Log("DPS initialized");
	}
	if($Function eq "GET") {
		return(DPS_GetData());
	} elsif ($Function eq "SEND") {
		return(DPS_SendData());
	} elsif ($Function eq "GET_RAW") {
		return(DPS_GetData(1));
	} elsif ($Function eq "GET_MD5") {
		return if not DPS_Connect();
		my $MD5Sum = DPS_DataSegment("GET_MD5");
		DPS_Disconnect();
		return($MD5Sum);
	} else {
		DPIntWarn("Invalid option to DPS_Perform: $Function. Ignoring");
		return(undef);
	}
}

# Purpose: Wrapper around DPS_Perform that creates the GUI widgets
# Usage: DPS_GUIPerform(FUNCTION)
# 	The FUNCTION argument is identical to DPS_Perform's FUNCTION argument
sub DPS_GUIPerform {
	my $Function = $_[0];
	return unless(defined($InternalConfig{DPS_Enable}) and $InternalConfig{DPS_Enable} eq "1");

	# Create the progress window
	$MainWindow->set_sensitive(0);
	$DPServices{ProgressWin} = DPCreateProgressWin(DP_gettext("Services"), DP_gettext("Initializing"));

	my $Return = DPS_Perform($Function);
	$DPServices{ProgressWin}->{Window}->destroy();
	if(defined($DPServices{Error})) {
		DPError(sprintf(DP_gettext("An error ocurred with the Day Planner services:\n\n%s"), $DPServices{Error}));
		delete($DPServices{Error});
	}
	delete($DPServices{ProgressWar});
	$MainWindow->set_sensitive(1);
	return($Return);
}

# Purpose: Set the status in the DPS GUI Window
# Usage: DPS_Status(TEXT, COMPLETED);
# 	COMPLETED is a number between 0 and 1
# 	0 = 0%
# 	1 = 100%
sub DPS_Status {
	my ($Text, $Completed) = @_;
	return unless($Gtk2Init);
	if(defined($DPServices{ProgressWin})) {
		$DPServices{ProgressWin}->{ProgressBar}->set_fraction($Completed);
		$DPServices{ProgressWin}->{ProgressBar}->set_text($Text);
		Gtk2->main_iteration while Gtk2->events_pending;
	}
}

# Purpose: Log DPS info
# Usage: DPS_Log(INFO);
sub DPS_Log {
	if(defined($DPServices{Log_FH})) {
		my ($lsec,$lmin,$lhour,$lmday,$lmon,$lyear,$lwday,$lyday,$lisdst) = localtime(time);
		$lhour = "0$lhour" unless $lhour >= 10;
		$lmin = "0$lmin" unless $lmin >= 10;
		$lsec = "0$lsec" unless $lsec >= 10;
		my $FH = $DPServices{Log_FH};
		print $FH "[$lhour:$lmin:$lsec] $_[0]\n";
	}
	return(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(DP_gettext(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",
		MainWin_X => "The X-axis position of the main window",
		MainWin_Y => "The Y-axist position 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\n# (this ONLY sets if it has been automatically added or not.\n# 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)",
		LastVersion => "The last Day Planner version used",
		# All except DPS_LastMD5 and DPS_ParanoidBackups actually belongs in dayplanner.conf
		# and not in state.conf. Maybe we should add an dps.conf/services.conf ?
		DPS_Enable => "If DPS (Day Planner servies) is enabled or not (1/0)",
		DPS_LastMD5 => "The MD5 sum of the last data downloaded from the DPS server (base64 encoded)",
		DPS_host => "The DPS host to connect to",
		DPS_pass => "The password",
		DPS_port => "The port to connect to on the DPS server",
		DPS_user => "The username",
		DPS_ParanoidBackups => "Writes a backup of the internal data to $Dir every time DPS is used.\n# This is useful when DPS is being debugged, in order to avoid data loss",
		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();
			($InternalConfig{MainWin_X}, $InternalConfig{MainWin_Y}) = $MainWindow->get_position();
		}
	}
	
	# Write the actual file
	WriteConfigFile("$Dir/$File", \%InternalConfig, \%Explenations);
}

# Purpose: Load the state file
# Usage: LoadStateFile(DIRECTORY, FILENAME);
sub LoadStateFile {
	# The parameters
	if(-e "$SaveToDir/state.conf") {
		LoadConfigFile("$SaveToDir/state.conf", \%InternalConfig, undef, 0);
		if(not defined($InternalConfig{LastVersion}) or not $InternalConfig{LastVersion} eq $Version) {
			UpgradeVersion();
		}
	} else {
		$InternalConfig{LastVersion} = $Version;
	}
}

# Purpose: Write the configuration file
# Usage: WriteConfig(DIRECTORY, FILENAME);
sub WriteConfig {
	# The parameters
	my ($Dir,$File) = ($SaveToDir, $ConfigFile);
	# 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") {
			DPIntWarn("Failed to tell the daemon to reload its configuration file. This might cause problems");
		}
	}
}

# Purpose: Load the configuration file
# Usage: LoadConfig();
sub LoadConfig {
	# The parameters
	my ($Dir,$File) = ($SaveToDir, $ConfigFile);
	# 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);
	return(1);
}

# 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(DP_gettext(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: Perform certain commands on first startup
# Usage: FirstStartup(GUI?);
sub FirstStartup {
	# First create the savedir
	CreateSaveDir();
	# Set LastVersion
	$InternalConfig{LastVersion} = $Version;
	if(not $_[0]) {
		# Attempt to import data
		return(ImportDataFromProgram(1));
	}
	return(0);
}	

# Purpose: Save the main data files
# Usage: SaveMainData();
sub SaveMainData {
	my $Return = 'OKAY';
	$iCalendar->write();	# TODO: Check return value
	# This to avoid unneccesary overhead in the daemon reloading files needlessly
	unless(Daemon_DataSegment("RELOAD_DATA") eq "done\n") {
		if($Gtk2Init) {
			DPWarning(DP_gettext("<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."));
		} else {
			DPWarning("Unable to tell the daemon to reload its data. This *could* be bad.");
		}
		if ($Return eq 'OKAY') {
			$Return = 'DAEMON_RELOAD_FAILURE';
		}
	}
	return($Return);
}

# Purpose: Save the data file and redraw the needed windows
# Usage: UpdatedData(DONT_SAVE?, DONT_REDRAW_EVENTLIST);
sub UpdatedData {
	my ($DontSave,$DontRedrawEventList) = @_;
	if(not $DontRedrawEventList) {
		# Redraw the event list
		PopulateEventList();
	}
	# Repopulate the upcoming events
	PopulateUpcomingEvents();
	# Redraw the calendar
	CalendarChange();
	# Lastly, save the main data if needed.
	# We do this last because that speeds up redrawing the calendar
	if(not $DontSave) {
		# Save the data
		SaveMainData();
	}
}

# Purpose: Load the calendar contents
# Usage: LoadCalendar();
sub LoadCalendar {
	my $IsFirstStartup = shift;
	if(-e "$SaveToDir/$ICSFile") {
		$iCalendar = DP::iCalendar->new("$SaveToDir/$ICSFile");
	} else {
		$iCalendar = DP::iCalendar->newfile("$SaveToDir/$ICSFile");
	}
	$iCalendar->set_prodid("-//EskildHustvedt//NONSGML Day Planner $Version//EN");
	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);
	}
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# General helper functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

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

# 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)");
}

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

# Purpose: Get the SUMMARY string for an UID
# Usage: my $Summary = GetSummaryString(UID);
#  The returned summary string is properly formatted (ie. it will return
#  a properly localized birthday string if needed).
sub GetSummaryString {
	my $UID = shift;
	my $UID_Obj = $iCalendar->get_info($UID);
	if(defined($UID_Obj->{'X-DP-BIRTHDAY'}) and $UID_Obj->{'X-DP-BIRTHDAY'} eq 'TRUE') {
		if($UID_Obj->{'X-DP-BIRTHDAYNAME'}) {
			return(sprintf(DP_gettext("%s's birthday"),$UID_Obj->{'X-DP-BIRTHDAYNAME'}));
		} else {
			DPIntWarn("UID $UID is set to be a birthday but is missing X-DP-BIRTHDAYNAME, using SUMMARY string");
		}
	}
	if(not $UID_Obj->{SUMMARY}) {
		DPIntWarn("No SUMMARY found for the UID $UID");
		return(DP_gettext("Unknown"));
	}
	return($UID_Obj->{SUMMARY});
}

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

# Purpose: Upgrade from one version to another
# Usage: UpgradeVersion();
sub UpgradeVersion {
	my $ProgressWin = DPCreateProgressWin(DP_gettext("Upgrading"), "", 1);
	$InternalConfig{LastVersion} = $Version;
	my $Total = 7;
	# Upgrade the holiday file if needed
	if(-e $HolidayFile) {
		ProgressMade(1, $Total, $ProgressWin) if($Gtk2Init);
		if(-e "$FindBin::RealBin/holiday/dayplanner_upgrade") {
			my %UpgradeInfo;
			LoadConfigFile("$FindBin::RealBin/holiday/dayplanner_upgrade", \%UpgradeInfo, undef, 0);
			ProgressMade(2, $Total, $ProgressWin) if($Gtk2Init);
			open(my $ReadHoliday, "<", $HolidayFile);
			my $HolidayMd5 = md5_hex(<$ReadHoliday>);
			close($ReadHoliday);
			if($UpgradeInfo{$HolidayMd5}) {
				if (-e "$FindBin::RealBin/holiday/$UpgradeInfo{$HolidayMd5}") {
					# Copy the new file in place
					unlink($HolidayFile);
					copy("$FindBin::RealBin/holiday/$UpgradeInfo{$HolidayMd5}", $HolidayFile);
					DPIntInfo("Upgraded $HolidayFile");
					ProgressMade(3, $Total, $ProgressWin) if($Gtk2Init);
				}
			}
		}
	}
	# Upgrade the old data files if needed
	if(not -e "$SaveToDir/$ICSFile") {
		# Normal events (events.dpd)
		if(-e "$SaveToDir/events.dpd") {
			my %CalendarContents = do("$SaveToDir/events.dpd");
			foreach my $Year(keys(%CalendarContents)) {
				foreach my $Month (keys(%{$CalendarContents{$Year}})) {
					foreach my $Day (keys(%{$CalendarContents{$Year}{$Month}})) {
						foreach my $Time (keys(%{$CalendarContents{$Year}{$Month}{$Day}})) {
							# Create the data hash
							my %iCalData;
							$iCalData{SUMMARY} = $CalendarContents{$Year}{$Month}{$Day}{$Time}{summary};
							if(defined($CalendarContents{$Year}{$Month}{$Day}{$Time}{fulltext}) and length($CalendarContents{$Year}{$Month}{$Day}{$Time}{fulltext})) {
								$iCalData{DESCRIPTION} = $CalendarContents{$Year}{$Month}{$Day}{$Time}{fulltext};
							}
							$iCalData{DTSTART} = iCal_GenDateTime($Year, $Month, $Day, $Time);
							$iCalData{DTEND} = $iCalData{DTSTART};
							$iCalendar->add(%iCalData);
						}
					}
				}
			}
		}
		ProgressMade(4, $Total, $ProgressWin) if($Gtk2Init);
		# Birthday events (birthdays.dpd)
		if(-e "$SaveToDir/birthdays.dpd") {
			my %BirthdayContents = do("$SaveToDir/$BirthdayFile");
			foreach my $Month (keys(%BirthdayContents)) {
				foreach my $Day (keys(%{$BirthdayContents{$Month}})) {
					foreach my $Name (keys(%{$BirthdayContents{$Month}{$Day}})) {
						# Create the data hash
						my %iCalData;
						$iCalData{DTSTART} = iCal_GenDateTime(1970, $Month, $Day, "00:00");
						$iCalData{DTEND} = $iCalData{DTSTART};
						$iCalData{'X-DP-BIRTHDAY'} = 'TRUE';
						$iCalData{'X-DP-BIRTHDAYNAME'} = $Name;
						$iCalData{RRULE} = 'FREQ=YEARLY';
						$iCalData{SUMMARY} = sprintf(DP_gettext("%s's birthday"), $Name);
						$iCalendar->add(%iCalData);
					}
				}
			}
		}
		ProgressMade(5, $Total, $ProgressWin) if($Gtk2Init);
		# Write the iCalendar file.
		$iCalendar->write();
		ProgressMade(6, $Total, $ProgressWin) if($Gtk2Init);
	}
	foreach(qw/special_events.dpd events.dpd birthdays.dpd/) {
		unlink("$SaveToDir/$_") if -e "$SaveToDir/$_";
	}
	ProgressMade(7, $Total, $ProgressWin) if($Gtk2Init);
	$ProgressWin->{Window}->destroy();
}

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

# Purpose: Print an info message to STDOUT with proper output
# Usage: DPIntInfo("Info");
sub DPIntInfo {
	print "*** (Day Planner $Version): $_[0]\n";
}

# 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\nClosing the daemon connection...";
	CloseDaemon();
	print "done\nExiting the gtk2 main loop...";
	Gtk2->main_quit;
	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);
	}

	# 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);
	}

	# Hash of LC_ADDRESS code => holiday file
	# Loaded from dayplanner_detection
	my %HolidayFiles;
	if(-e "$FindBin::RealBin/holiday/dayplanner_detection") {
		LoadConfigFile("$FindBin::RealBin/holiday/dayplanner_detection", \%HolidayFiles, undef, 0)
			or DPIntWarn("Unable to load holiday detection definitions!");
	}

	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: 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(DP_gettext("A problem occurred while setting up automatic startup of the Day Planner reminder.\n\nSet it up manually by selecting: menu -> desktop -> settings -> sessions. From there select \"Startup Programs\", click \"Add\". Type \"dayplanner-daemon\" in the \"Startup Command\" field and press the \"OK\" button."));
		}
	} 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(DP_gettext("Disabling automatic startup of the reminder will prevent notifications unless Day Planner has been manually started. Disable the automatic startup?"))) {
		$InternalConfig{AutostartOn} = 1;
		return(RemoveAutostart());
	}
	return(undef);
}

# 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();
	# TODO: Switch days if the currently selected day is yesterday
	# 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);
}

# Purpose: Call the main initialization functions
# Usage: MainInit();
sub MainInit {
	my $NoGui = $_[0];
	my $FirstStartup;
	# 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

	unless(-d $SaveToDir) {
		$FirstStartup =	FirstStartup($NoGui);
	}

	# Load the configuration file
	LoadConfig();
	# Load the calendar
	LoadCalendar();
	# Load the internal state file
	LoadStateFile();
	# Set up holidays
	HolidaySetup();
	return($FirstStartup);
}

# Purpose: Convert AM/PM to internal 24H time
# Usage: AMPM_To24(TIME [AM|PM]);
sub AMPM_To24 {
	my ($Time, $MultiRet) = @_;
	return($Time) if $ClockSystem eq "24";
	my $Hour = $Time;
	my $Minutes = $Time;
	my $Suffix = $Time;
	$Hour =~ s/^(\d+):.*$/$1/;
	$Minutes =~ s/^\d+:(\d+).*$/$1/;
	$Suffix =~ s/^\d+:\d+\s+(.+)/$1/;
	if($Suffix eq $PM_String) {
		$Hour = $Hour+12;
	} elsif($Suffix eq $AM_String) {
		if($Hour == 12) {
			$Hour = "00";
		}
	}
	if($MultiRet) {
		return($Hour, $Minutes);
	} else {
		return("$Hour:$Minutes");
	}
}

# Purpose: Convert internal 24H time to AM/PM
# Usage: AM_PM_From24(TIME);
sub AMPM_From24 {
	my $Time = $_[0];
	return($Time) if $ClockSystem eq "24";
	my $Hour = $Time;
	my $Minutes = $Time;
	my $Suffix;
	$Hour =~ s/^(\d+):.*$/$1/;
	$Minutes =~ s/^\d+:(\d+).*$/$1/;
	if($Hour >= 12) {
		$Suffix = $PM_String;
		$Hour = $Hour-12;
	}
	unless(defined($Suffix)) {
		if($Hour == 0) {
			$Hour = "12";
		}
		$Suffix = $AM_String;
	}
	return("$Hour:$Minutes $Suffix");
}

# =============================================================================
# IMPORT/EXPORT
# =============================================================================

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Various utility functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Commandline wrapper around the export functions
# Usage: 'x|yzx' => \&CLI_Export,
sub CLI_Export {
	# First initialize the i18n system
	DP_InitI18n();
	# Then initialize the data
	my($Type,$Target) = @_;
	MainInit(1);
	if($Type =~ /(ical|ics_dps)/) {
		unless(-w dirname($Target)) {
			die("Unable to write to " . dirname($Target) . "\n");
		}
		if(-d $Target) {
			die("$Target: is a directory\n");
		}
		if($Type =~ /(ical|ics_dps)/) {
			if($iCalendar->write($Target)) {
				print "iCalendar data written to $Target\n";
			}
		}
	} elsif($Type =~ /(html|php)/) {
		if(-e $Target) {
			unless(-w $Target) {
				die("I don't have write permission to $Target\n");
			}
			unless(-d $Target) {
				die("$Target: is not a directory\n");
			}
		}
		if($Type =~ /html/) {
			if(HTML_Export($Target)) {
				print "HTML written to $Target\n";
			}
		} elsif ($Type =~ /php/) {
			if(PHP_Export($Target)) {
				print "PHP written to $Target\n";
			}
		}
	} else {
		die("Unknown type given as argument to CLI_Export: $Type\n");
	}
	exit(0);
}

# Purpose: Commandline wrapper around the import functions
# Usage: 'x|yzx' => \&CLI_Import,
sub CLI_Import {
	my($Type,$Source) = @_;
	MainInit(1);
	die("$Source: does not exist\n") unless(-e $Source);
	die("$Source: is not readable by me\n") unless(-r $Source);
	die("$Source: is a directory\n") if (-d $Source);
	# Initialize the daemon (we need it)
	DaemonInit() or die();
	if($Type =~ /ical/) {
		if($iCalendar->addfile($Source)) {
			print "Imported iCalendar data from $Source\n";
		} else {
			print "Importing failed.\n";
		}
	}
	# Save the data
	SaveMainData();
	# Close the daemon connection
	CloseDaemon();
	# Exit peacefully
	exit(0);
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# HTML export functions (almost all of these are shared with the PHP exporter)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Encode special HTML entities
# Usage: HTML_Encode(STRING);
sub HTML_Encode {
	my $String = shift;
	study($String);
	$String =~ s#\n#<br />#g;
	$String =~ s/&/&amp;/g;
	$String =~ s/</&lt;/g;
	$String =~ s/>/&gt;/g;
	$String =~ s/"/&quot;/g;
	return($String);
}

# Purpose: Output the header for all Day Planner HTML files
# Usage: print $FILE HTML_Header(YEAR,DATE,NONDATEMODE?);
sub HTML_Header {
	return('') if $HTML_PHP;
	my ($Year,$Date,$NonDateMode) = @_;
	my $Header = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
	$Header .= "<html><head>\n";
	$Header .= '<meta content="text/html; charset=iso-8859-1" http-equiv="content-type" />';
	$Header .= "<meta name='generator' content='Day Planner $Version - http://www.day-planner.org' />\n";;
	if($NonDateMode) {
		$Header .= "<title>" . HTML_Encode($Date) . "</title>";
	} else {
		$Header .= "<title>" . HTML_Encode(sprintf(DP_gettext("Day Planner for %s"),$Date)) . "</title>";
	}
	$Header .= "</head>\n";
	$Header .= '<body>';
	$Header .= "<h2>" . HTML_Encode($Date) . "</h2>";
	$Header .= "<!-- Generated by Day Planner version $Version - $RCSRev -->\n";
	$Header .= '<hr />';
	$Header .= HTML_Menu(@_);
	return($Header);
}

# Purpose: Print the menu for a Day Planner HTML doc
# Usage: print $FILE HTML_Menu(YEAR,DATE,NONDATEMODE?);
sub HTML_Menu {
	my ($Year,$Date,$NonDateMode) = @_;
	my $Menu;
	my %Links;
	if($HTML_PHP) {
		# PHP-style links
		%Links = (
			1 => 'index.php?year=<?php print "$Year" ?>&amp;month=1',
			2 => 'index.php?year=<?php print "$Year" ?>&amp;month=2',
			3 => 'index.php?year=<?php print "$Year" ?>&amp;month=3',
			4 => 'index.php?year=<?php print "$Year" ?>&amp;month=4',
			5 => 'index.php?year=<?php print "$Year" ?>&amp;month=5',
			6 => 'index.php?year=<?php print "$Year" ?>&amp;month=6',
			7 => 'index.php?year=<?php print "$Year" ?>&amp;month=7',
			8 => 'index.php?year=<?php print "$Year" ?>&amp;month=8',
			9 => 'index.php?year=<?php print "$Year" ?>&amp;month=9',
			10 => 'index.php?year=<?php print "$Year" ?>&amp;month=10',
			11 => 'index.php?year=<?php print "$Year" ?>&amp;month=11',
			12 => 'index.php?year=<?php print "$Year" ?>&amp;month=12',
			yearindex => "index.php?page=yearindex",
		);
	} else {
		# HTML-style links
		%Links = (
			1 => "january-$Year.html",
			2 => "february-$Year.html",
			3 => "march-$Year.html",
			4 => "april-$Year.html",
			5 => "may-$Year.html",
			6 => "june-$Year.html",
			7 => "july-$Year.html",
			8 => "august-$Year.html",
			9 => "september-$Year.html",
			10 => "october-$Year.html",
			11 => "november-$Year.html",
			12 => "december-$Year.html",
			yearindex => "index.html",
		);
	}
	if(defined($NonDateMode) and not $NonDateMode eq "M") {
		$Menu .= HTML_Encode(DP_gettext("Tools")) . ": <a href='$Links{yearindex}'>" . HTML_Encode(DP_gettext("View the main page")) . "</a><br/>\n";
	} elsif(not defined($NonDateMode) or not $NonDateMode eq "M") {
		$Menu .= HTML_Encode(DP_gettext("Tools")) . ": <a href='$Links{yearindex}'>" . HTML_Encode(DP_gettext("Change to another year")) . "</a> (" . HTML_Encode(sprintf(DP_gettext("current: %s"), $Year)) . ") - </a><br />\n";
		$Menu .= HTML_Encode(DP_gettext("Months")) .
			": <a href='$Links{1}'>" . HTML_Encode($MonthNames{1}) .
			"</a> - <a href='$Links{2}'>" . HTML_Encode($MonthNames{2}) .
		       	"</a> - <a href='$Links{3}'>" . HTML_Encode($MonthNames{3}) .
			"</a> - \n<a href='$Links{4}'>" . HTML_Encode($MonthNames{4}) .
			"</a> - <a href='$Links{5}'>" . HTML_Encode($MonthNames{5}) .
			"</a> - <a href='$Links{6}'>" . HTML_Encode($MonthNames{6}) .
			"</a> - \n<a href='$Links{7}'>" . HTML_Encode($MonthNames{7}) .
			"</a> - <a href='$Links{8}'>" . HTML_Encode($MonthNames{8}) .
			"</a> - <a href='$Links{9}'>" . HTML_Encode($MonthNames{9}) .
			"</a> - \n<a href='$Links{10}'>" . HTML_Encode($MonthNames{10}) .
			"</a> - <a href='$Links{11}'>" . HTML_Encode($MonthNames{11}) .
			"</a> - <a href='$Links{12}'>" . HTML_Encode($MonthNames{12}) . "</a><br/>\n";
	}
	return($Menu);
}

# Purpose: Print the footer of all Day Planner HTML documents
# Usage: print $FILE HTML_Footer();
sub HTML_Footer {
	return('') if $HTML_PHP;
	my $Footer = "<br /><small><small>" . HTML_Encode(DP_gettext("Generated by")) .  " <a href='http://www.day-planner.org/'>" . HTML_Encode(DP_gettext("Day Planner")) . "</a> " . HTML_Encode(DP_gettext("version")) . " $Version</small></small><br />";
	$Footer .= "</body></html>";
	return($Footer);
}

# Purpose: Output a specified day to HTML
# Usage: HTML_DayToHtml(YEAR,MONTH,DAY,DIRECTORY);
sub HTML_DayToHtml {
	my ($Year,$Month,$Day,$Directory) = @_;
	my (@AllDay, @OtherEvents);
	open(my $FILE, ">", "$Directory/dp_$Year$Month$Day.html") or do {
		DPIntWarn("Unable to open $Directory/dp_$Year$Month$Day.html for writing: $!");
		DPError(sprintf(DP_gettext("Unable to open %s for writing: %s"), "$Directory/dp_$Year$Month$Day.html", $!));
		return(undef);
	};
	# Header
	print $FILE HTML_Header($Year,"$Day-$Month-$Year");
	print $FILE '<table style="text-align: left;" border="1" cellpadding="2" cellspacing="2">';
	print $FILE "<tbody><tr><td>" . HTML_Encode(DP_gettext("Time")) . "</td><td>" . HTML_Encode(DP_gettext("Description")) . "</td></tr>\n";
	# Write allday and birthdays
	foreach my $Event (sort (@{$iCalendar->get_timeinfo($Year,$Month,$Day,"DAY")})) {
		print $FILE "<tr><td></td><td>" . HTML_Encode(GetSummaryString($Event));
		my $EventInfo = $iCalendar->get_info($Event);
		if(defined($EventInfo->{DESCRIPTION})) {
			my $HTML_Fulltext = HTML_Encode($EventInfo->{DESCRIPTION});
			print $FILE "<br /><i>$HTML_Fulltext</i>";
		}
		print $FILE "</td></tr>\n";
	}
	# Write others
	foreach my $Time (sort(@{$iCalendar->get_dateinfo($Year,$Month,$Day)})) {
		next if $Time eq 'DAY';
		foreach my $Event (sort (@{$iCalendar->get_timeinfo($Year,$Month,$Day,$Time)})) {
			print $FILE "<tr><td>$Time</td><td>" . HTML_Encode(GetSummaryString($Event));
			my $EventInfo = $iCalendar->get_info($Event);
			if(defined($EventInfo->{DESCRIPTION})) {
				my $HTML_Fulltext = HTML_Encode($EventInfo->{DESCRIPTION});
				print $FILE "<br /><i>$HTML_Fulltext</i>";
			}
			print $FILE "</td></tr>\n";
		}
	}
	# Prepare and write holidays
	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}})) {
			print $FILE "<tr><td></td><td> " . HTML_Encode($CurrHoliday) . "</td></tr>\n";
		}
	}
	print $FILE '</tbody></table>';
	print $FILE HTML_Footer();
	close($FILE);
}

# Purpose: Output a specified month in HTML
# Usage: HTML_MonthToHtml
sub HTML_MonthToHtml {
	my ($Year,$Month,$Directory) = @_;
	my %RawMonthNames = (
		1 => "january",
		2 => "february",
		3 => "march",
		4 => "april",
		5 => "may",
		6 => "june",
		7 => "july",
		8 => "august",
		9 => "september",
		10 => "october",
		11 => "november",
		12 => "december"
	);
	open(my $FILE, ">", "$Directory/$RawMonthNames{$Month}-$Year.html") or do {
		DPIntWarn("Unable to open $Directory/$RawMonthNames{$Month}-$Year.html for writing: $!");
		DPError(sprintf(DP_gettext("Unable to open %s for writing: %s"), "$RawMonthNames{$Month}-$Year.html", $!));
		return(undef);
	};
	print $FILE HTML_Header($Year, "$MonthNames{$Month} $Year");
	my $HadContent;
	my $MonthInfo = $iCalendar->get_monthinfo($Year,$Month);
	foreach my $Day (sort @{$MonthInfo}) {
		$HadContent = 1;
		print $FILE "<a href='dp_$Year$Month$Day.html'>" . HTML_Encode("$Day. $MonthNames{$Month} $Year") . "</a><br/>\n";
	}
	unless($HadContent) {
		print $FILE "<i>" . HTML_Encode(DP_gettext("There are no events this month")) . "</i>";
	}
	# Prepare and write holidays
	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})) {
		print $FILE "<h4>" . HTML_Encode(DP_gettext("Special days this month:")) . "</h4>";
		foreach my $Day (sort {$a <=> $b} keys %{$Holidays{$Year}->{$Month}}) {
			foreach my $CurrHoliday (keys(%{$Holidays{$Year}->{$Month}{$Day}})) {
				print $FILE HTML_Encode("$Day. $MonthNames{$Month}: $CurrHoliday") . "<br/>\n"
			}
		}
	}
	print $FILE HTML_Footer();
	close($FILE);
}

# Purpose: Output all birthdays to a file
# Usage: HTML_BirthdayList(DIRECTORY);
sub HTML_BirthdayList {
	warn("HTML_BirthdayList: STUBBED, using \%BirthdayContents!");
=cut
	my($Directory) = @_;
	open(my $FILE, ">", "$Directory/birthdays.html") or do {
		DPIntWarn("Unable to open $Directory/birthdays.html for writing: $!");
		DPError(sprintf(DP_gettext("Unable to open %s for writing: %s"), "$Directory/birthdays.html", $!));
		return(undef);
	};
	print $FILE HTML_Header(undef,DP_gettext("Birthdays"),1);
	if(keys(%BirthdayContents)) {
		print $FILE '<table style="text-align: left;" border="1" cellpadding="2" cellspacing="2">';
		print $FILE "<tbody><tr><td>" . HTML_Encode(DP_gettext("Date")) . "</td><td>" . HTML_Encode(DP_gettext("Name")) . "</td></tr>\n";
		foreach my $Month (sort {$a <=> $b}  keys(%BirthdayContents)) {
			foreach my $Day (sort {$a <=> $b} keys(%{$BirthdayContents{$Month}})) {
				my $PrintDay = AppendZero($Day);
				foreach my $Name (sort(keys(%{$BirthdayContents{$Month}{$Day}}))) {
					print $FILE "<tr><td>" . HTML_Encode("$PrintDay $MonthNames{$Month}") . "</td><td>" . HTML_Encode($Name) . "</td></tr>\n";
				}
			}
		}
		print $FILE '</tbody></table>';
	} else {
		print $FILE . "<i>" .  HTML_Encode(DP_gettext("No birthdays are defined.")) . "</i><br/>";
	}
	print $FILE HTML_Footer();
=cut
}

# Purpose: Output a year information page to HTML
# Usage: HTML_YearHtml(YEAR,DIRECTORY);
sub HTML_YearHtml {
	my ($Year, $Directory) = @_;
	open(my $FILE, ">", "$Directory/$Year.html") or do {
		DPIntWarn("Unable to open $Directory/$Year.html for writing: $!");
		DPError(sprintf(DP_gettext("Unable to open %s for writing: %s"), "$Directory/$Year.html", $!));
		return(undef);
	};
	print $FILE HTML_Header($Year, $Year, 0);
	print $FILE HTML_Encode(DP_gettext("Select the month to view in the list above")) . "<br />\n";
	print $FILE HTML_Footer();
}

# Purpose: Output a list of years (aka. the index page) to HTML
# Usage: HTML_YearList(DIRECTORY);
sub HTML_YearList {
	my ($Directory) = @_;
	open(my $FILE, ">", "$Directory/index.html") or do {
		DPIntWarn("Unable to open $Directory/index.html for writing: $!");
		DPError(sprintf(DP_gettext("Unable to open %s for writing: %s"), "$Directory/index.html", $!));
		return(undef);
	};
	print $FILE HTML_Header("", "Day Planner", "M");
	print $FILE HTML_Encode(DP_gettext("Select the year to view:")) . "<br />\n";
	foreach(@{$iCalendar->get_years()}) {
		print $FILE "<a href='$_.html'>" . HTML_Encode($_) . "</a><br />\n";
	}
	print $FILE HTML_Footer();
}

# Purpose: Output a php file that adds autodetection of todays date to
# 	Day Planner HTML exports. (NOTE: Not used for actual PHP exporting)
# Usage: HTML_PHPIndex(DIRECTORY);
sub HTML_PHPIndex {
	my ($Directory) = @_;
	open(my $FILE, ">", "$Directory/index.php") or do {
		DPIntWarn("Unable to open $Directory/index.php for writing: $!");
		DPError(sprintf(DP_gettext("Unable to open %s for writing: %s"), "$Directory/index.php", $!));
		return(undef);
	};
	print $FILE "<?php\n// This is a simple script written by Day Planner to add autodetection of\n// the current day to exported Day Planner HTML sites. Only useful on\n// webservers with php support.\n// See also Day Planners php export feature for a more complete\n// PHP export of Day Planner data.\n// Copyright (C) Eskild Hustvedt 2006. Licensed under the same license as day planer\n";
	print $FILE HTML_PHP_DayDetectFunc();
	print $FILE '$file = DayDetectFunc("./");' . "\n";
	print $FILE 'if($file) {' . "\n";
	print $FILE "\t" . 'include($file)';
	print $FILE "} else {" . "\n";
	print $FILE "\t" . 'print("Unable to detect files. This export is corrupt!");' . "\n";
	print $FILE "}\n?>";
}

# Purpose: Export Day Planner data to HTML
# Usage: HTML_Export(DIRECTORY);
sub HTML_Export {
	my %RawMonthNames = (
		1 => "january",
		2 => "february",
		3 => "march",
		4 => "april",
		5 => "may",
		6 => "june",
		7 => "july",
		8 => "august",
		9 => "september",
		10 => "october",
		11 => "november",
		12 => "december"
	);
	my $Dir = $_[0];
	unless(-d $Dir) {
		eval("mkpath('$Dir')");
		if($@) {
			DPIntWarn("Unable to mkpath($Dir): $@");
			DPError(sprintf(DP_gettext("Unable to create the directory %s: %s", $Dir, $@)));
			return(undef);
		}
	}
	foreach my $Year (@{$iCalendar->get_years}) {
		HTML_YearHtml($Year,$Dir);
		foreach my $Month (@{$iCalendar->get_months($Year)}) {
			foreach my $Day (@{$iCalendar->get_monthinfo($Year,$Month)}) {
				HTML_DayToHtml($Year, $Month, $Day, $Dir);
			}
		}
		foreach(1..12) {
			HTML_MonthToHtml($Year,$_,$Dir);
		}
	}
	HTML_YearList($Dir);
	HTML_PHPIndex($Dir);
	# TODO: Either FIXME or DROPME!
#	HTML_BirthdayList($Dir);
}

# Purpose: Function to detect todays day using php
# Usage: print HTML_PHP_DayDetectFunc();
sub HTML_PHP_DayDetectFunc {
	my $Return = 'function DayDetectFunc ($datadir) {' . "\n";
	$Return .= "\t" . '$Year = date("Y");' . "\n";
	$Return .= "\t" . '$Month = date("n");' . "\n";
	$Return .= "\t" . '$Day = date("j");' . "\n";
	$Return .= "\t" . '$Months = array(1 => "january", 2 => "february", 3 => "march", 4 => "april", 5 => "may", 6 => "june", 7 => "july", 8 =>"august", 9 =>"september", 10 => "october", 11 => "november", 12 =>"december");' . "\n";
	$Return .= "\t" . 'if(file_exists("$datadir/dp_$Year$Month$Day.html")) {' . "\n";
	$Return .= "\t\t" . 'return("$datadir/dp_$Year$Month$Day.html");' . "\n";
	$Return .= "\t" . '} elseif(file_exists("$datadir/$Months[$Month]-$Year.html")) {' . "\n";
	$Return .= "\t\t" . 'return("$datadir/$Months[$Month]-$Year.html");' . "\n";
	$Return .= "\t" . '} elseif(file_exists("$datadir/index.html")) {' . "\n";
	$Return .= "\t\t" . 'return("$datadir/index.html");' . "\n";
	$Return .= "\t} else {" . "\n";
	$Return .= "\t\treturn 0;\n";
	$Return .= "\t}\n}\n";
	return($Return);
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# PHP export functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Export Day Planner data to PHP/HTML
# Usage: PHP_Export(DIRECTORY);
sub PHP_Export {
	$HTML_PHP = 1;
	# FIXME: Code from HTML_Export needlessly copied here
	my %RawMonthNames = (
		1 => "january",
		2 => "february",
		3 => "march",
		4 => "april",
		5 => "may",
		6 => "june",
		7 => "july",
		8 => "august",
		9 => "september",
		10 => "october",
		11 => "november",
		12 => "december"
	);
	my $Dir = $_[0];
	my $BaseDir = $Dir;
	$Dir = $Dir . "/dp_data/";
	unless(-d $Dir) {
		eval("mkpath('$Dir')");
		if($@) {
			DPIntWarn("Unable to mkpath($Dir): $@");
			DPError(sprintf(DP_gettext("Unable to create the directory %s: %s", $Dir, $@)));
			return(undef);
		}
	}
=cut
	foreach my $Year (keys(%CalendarContents)) {
		HTML_YearHtml($Year,$Dir);
		foreach my $Month (keys(%{$CalendarContents{$Year}})) {
			foreach my $Day (keys(%{$CalendarContents{$Year}{$Month}})) {
				HTML_DayToHtml($Year, $Month, $Day, $Dir);
			}
		}
		foreach(1..12) {
			HTML_MonthToHtml($Year,$_,$Dir);
		}
	}
=cut
	warn("PHP_Export: STUBBED");
	HTML_BirthdayList($Dir);
	$HTML_PHP = 0;
	PHP_WriteFiles($BaseDir, $Dir);
}

# Purpose: Create the PHP files used by the Day Planner PHP/HTML UI
# Usage: PHP_WriteFiles(DIRECTORY);
sub PHP_WriteFiles {
	my ($BaseDir, $DataDir) = @_;
	# Write the header and footer files
	# ...
	open(my $INDEX, ">", "$BaseDir/index.php");
	# Write index.php
	print $INDEX "<?php\n// Day Planner index.php\n// Copyright (C) Eskild Hustvedt 2006\n// Licensed under the GNU General Public License version 2 or newer\n// as published by the Free Software Foundation\n\n";
	print $INDEX '$year = $_GET[\'year\'];' . "\n";
	print $INDEX '$month = $_GET[\'month\'];' . "\n";
	print $INDEX '$day = $_GET[\'day\'];' . "\n";
	print $INDEX '$page = $_GET[\'page\'];' . "\n";
	# NOTES:
	# $page should always be used if present, but should also be limited to one of
	# birthdays and yearindex
	#
	# TODO: Drop yearindex in favor of a drop-down box?
	print $INDEX "// Begin by including the header here\n";
	print $INDEX "// Include the page here\n";
	print $INDEX "?>";
	close($INDEX);
	
	# TODO: Write a dummy index.html that just redirects to ../
	# 	maybe this could just be done using a .htaccess file too
	# 	but if so, have both so that it'll work on non-apache httpds.
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Plan migration functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Purge the current $PlanConvertHash-> buffer and put it into %CalendarContents
# Usage: PlanConvert_PurgeBuffer(\%PlanConvertHash);
sub PlanConvert_PurgeBuffer {
	my $PlanConvertHash = $_[0];
	my $ErrorMessage;
	if(defined($PlanConvertHash->{Day})) {
		if ($PlanConvertHash->{Type} eq 'normal') {
			my %TargetEvent;
			$TargetEvent{DTSTART} = iCal_GenDateTime($PlanConvertHash->{Year},$PlanConvertHash->{Month},$PlanConvertHash->{Day},$PlanConvertHash->{Time});
			$TargetEvent{DTEND} = $TargetEvent{DTSTART};
			delete($PlanConvertHash->{Day});
			delete($PlanConvertHash->{Month});
			delete($PlanConvertHash->{Year});
			delete($PlanConvertHash->{Time});
			if (not defined($PlanConvertHash->{Summary})) {
				DPIntWarn("Plan importer: Event at $TargetEvent{DTSTART} did not have a summary. Skipping.");
				return(undef);
			}
			$TargetEvent{SUMMARY} = $PlanConvertHash->{Summary};
			if (defined($PlanConvertHash->{Fulltext})) {
				$TargetEvent{DESCRIPTION} = $PlanConvertHash->{Fulltext};
				delete($PlanConvertHash->{Fulltext});
			}
			delete($PlanConvertHash->{Summary});
			$iCalendar->add(%TargetEvent);
		} elsif ($PlanConvertHash->{Type} eq 'bday') {
			my %TargetEvent;
			$TargetEvent{DTSTART} = iCal_GenDateTime($PlanConvertHash->{Year},$PlanConvertHash->{Month},$PlanConvertHash->{Day});
			$TargetEvent{DTEND} = $TargetEvent{DTSTART};
			delete($PlanConvertHash->{Day});
			delete($PlanConvertHash->{Month});
			delete($PlanConvertHash->{Year});
			if (not defined($PlanConvertHash->{Summary})) {
				DPIntWarn("Plan importer: Event at $TargetEvent{DTSTART} did not have a summary. Skipping.");
				next;
			}
			$TargetEvent{'X-DP-BIRTHDAY'} = 'TRUE';
			$TargetEvent{'X-DP-BIRTHDAYNAME'} = $PlanConvertHash->{Summary};
			$TargetEvent{RRULE} = 'FREQ=YEARLY';
			$TargetEvent{SUMMARY} = sprintf(DP_gettext("%s's birthday"), $PlanConvertHash->{Summary});
			delete($PlanConvertHash->{Fulltext});
			delete($PlanConvertHash->{Summary});
			$iCalendar->add(%TargetEvent);
		} else {
			DPIntWarn("BUG!!! Invalid type: $PlanConvertHash->{Type}. This could result in dangerous errors.");
		}
		$PlanConvertHash->{Type} = 'normal';
	}
}

# Purpose: Convert the file supplied
# Usage: PlanConvert_PurgeBuffer(/path/to/file,\%PlanConvertHash, $ProgressBar);
sub PlanConvert_ProcessFile {
	my $PlanConvertHash = $_[1]; 
	$PlanConvertHash->{Type} = 'normal';

	open(PLAN_FILE,$_[0]) or do {
		DPIntWarn("Unable to open $_[0]: $!");
		return(0);
	};
	my $LineNo = 0;
	while(<PLAN_FILE>) {
		$LineNo++;
		next if /^\s*(O|o|t|e|l|a|y|P|p|m|L|u|E)/;	# These are Plan specific stuff, just ignore them
		chomp;
		if (/^\s*(N)/) {		# Entry equalent to the dayplanner summary
			my $Summary = $_;
			$Summary =~ s#^\s*N\s+(.*)#$1#;
			$PlanConvertHash->{Summary} = $Summary;
		} elsif (/^\s*(M)/) {		# Entry equalent to the dayplanner fulltext
			my $Fulltext = $_;
			$Fulltext =~ s#^\s*M\s+(.*)#$1#;
			if(defined($PlanConvertHash->{Fulltext})) {
				$PlanConvertHash->{Fulltext} = "$PlanConvertHash->{Fulltext} $Fulltext";
			} else {
				$PlanConvertHash->{Fulltext} = $Fulltext;
			}
		} elsif (/^\s*(R)/) {		# These we just skip but might parse at some point
			if (/^\s*R\s+0\s+0\s+0\s+0\s+1/) {
				$PlanConvertHash->{Type} = 'bday';
			} else {
				next;
			}
		} elsif (/^\s*\d/) {		# Okay, it starts with a digit, it's a new date
			PlanConvert_PurgeBuffer($PlanConvertHash);
			my ($Day,$Month,$Year,$Time) = ($_,$_,$_,$_);
			# Get the day
			$Day =~ s#^\s*\d+/(\d+)/.*#$1#;
			# Get the month
			$Month =~ s#^\s*(\d+)/\d+/.*#$1#;
			# Get the year
			$Year =~ s#^\s*\d+/\d+/(\d+)\s+.*#$1#;
			# Get the time
			$Time =~ s#\s*\d+/\d+/\d+\s+(\d+:\d+):\d+\s+.*#$1#;
			# Convert the time to a more dayplannerish format
			$Time = "00:00" if $Time eq '99:99';
			if ($Time =~ /^\d+:\d$/) {
				$Time = $Time . 0;
			}
			if ($Time =~ /^\d:\d*$/) {
				$Time = 0 . $Time;
			}
			# Set the variables in the hash
			$PlanConvertHash->{Day} = $Day;
			$PlanConvertHash->{Month} = $Month;
			$PlanConvertHash->{Year} = $Year;
			$PlanConvertHash->{Time} = $Time;
		} else {
			DPIntWarn("WARNING: Unrecognized line (please report this): $_[0]:$LineNo: $_");
			next;
		}
	}
	return(1);
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# GUI import/export dialogs and helper functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Pop up a graphical dialog for importing data
# Usage: ImportData();
sub ImportData {
	$MainWindow->set_sensitive(0);
	# Create the window and VBox
	my $ImportWindow = Gtk2::Window->new();
	$ImportWindow->set_modal(1);
	$ImportWindow->set_transient_for($MainWindow);
	$ImportWindow->set_position('center-on-parent');
	$ImportWindow->set_title(DP_gettext("Import data"));
	$ImportWindow->set_resizable(0);
	$ImportWindow->set_border_width(12);
	$ImportWindow->set_skip_taskbar_hint(1);
	$ImportWindow->set_skip_pager_hint(1);
	$ImportWindow->signal_connect("destroy" => sub {
			$MainWindow->set_sensitive(1);
		});
	my $ImportVBox = Gtk2::VBox->new();
	$ImportWindow->add($ImportVBox);

	# Create the initial text
	my $ImportLabel = Gtk2::Label->new(DP_gettext('Import data from:'));
	$ImportVBox->pack_start($ImportLabel,0,0,0);

	# Create the radio buttons for selection and pack them onto the VBox
	my %StateHash;
	my $ActiveButton = "dayplanner";
	my $FromFileButton = Gtk2::RadioButton->new (undef, DP_gettext('A file'));
	my $FromProgramButton = Gtk2::RadioButton->new ($FromFileButton, DP_gettext('Other applications'));
	$ImportVBox->pack_start($FromFileButton,0,0,0);
	$ImportVBox->pack_start($FromProgramButton,0,0,0);

	# Add the buttons
	my $ButtonHBox = Gtk2::HBox->new();
	$ButtonHBox->show();
	$ImportVBox->pack_end($ButtonHBox,0,0,0);

	# Ok button
	my $OKButton = Gtk2::Button->new_from_stock('gtk-ok');
	$OKButton->show();
	$OKButton->can_default(1);
	$ImportWindow->set_default($OKButton);
	# Signal callback (starts a function depending on the radio button selected)
	$OKButton->signal_connect("clicked" => sub {
			$ImportWindow->destroy();
			if($FromFileButton->get_active) {
				ImportDataFromFile();
			} elsif ($FromProgramButton->get_active) {
				ImportDataFromProgram();
			} else {
				DPIntWarn("Unknown active button!\n");
			}
		});
	$ButtonHBox->pack_end($OKButton,0,0,0);

	# Cancel button
	my $CancelButton = Gtk2::Button->new_from_stock('gtk-cancel');
	$CancelButton->show();
	$ButtonHBox->pack_end($CancelButton,0,0,0);
	# Signal callback (destroys the window)
	$CancelButton->signal_connect("clicked" => sub {
			$ImportWindow->destroy();
		});

	# Add tooltips
	my $Tooltips = Gtk2::Tooltips->new();
	$Tooltips->set_tip($FromFileButton, DP_gettext("Import data from a file"));
	$Tooltips->set_tip($FromProgramButton, DP_gettext("Import data from various other applications"));
	$Tooltips->set_tip($CancelButton, DP_gettext('Cancel importing and return to Day Planner'));
	$Tooltips->set_tip($OKButton, DP_gettext('Continue'));

	# Show it all
	$Tooltips->enable();
	$FromFileButton->show();
	$FromProgramButton->show();
	$ImportLabel->show();
	$ImportVBox->show();
	$ImportWindow->show();
}

# Purpose: Pop up a import dialog for importing from other programs
# Usage: ImportDataFromProgram(IS_FIRST_TIME);
sub ImportDataFromProgram {
	my $FirstTime = $_[0];
	my $ProgressWindow = DPCreateProgressWin(DP_gettext("Preparing"), DP_gettext("Preparing to import"), 0);

	my %Programs = (
		Evolution => 0,
		Plan => 0,
		GnomeCalendar => 0,
		Korganizer => 0,
		ImportPossible => 0,
		TotalPrograms => 4,
	);

	# Detect evolution
	if(-e "$ENV{HOME}/.evolution/calendar/local/system/calendar.ics") {
		$Programs{Evolution} = 1;
		$Programs{ImportPossible} = 1;
	}
	ProgressMade($Programs{TotalPrograms}, 1, $ProgressWindow);

	# Detect Gnome Calendar
	if(-e "$ENV{HOME}/.gnome/user-cal.vcf") {
		$Programs{GnomeCalendar} = 1;
		$Programs{ImportPossible} = 1;
	}
	ProgressMade($Programs{TotalPrograms}, 2, $ProgressWindow);

	# Detect plan
	foreach my $Dir ("$ENV{HOME}/.plan","$ENV{HOME}/.plan.dir") {
		if (defined($Dir) and -d $Dir and -e "$Dir/dayplan") {
			$Programs{ImportPossible} = 1;
			$Programs{Plan} = 1;
		}
	}
	ProgressMade($Programs{TotalPrograms}, 3, $ProgressWindow);
	# Detect Korganizer
	if (-e "$ENV{HOME}/.kde/share/apps/korganizer/std.ics") {
		$Programs{Korganizer} = 1;
		$Programs{ImportPossible} = 1;
	}

	$ProgressWindow->{Window}->destroy();
	if(not $Programs{ImportPossible}) {
		if(not $FirstTime) {
			$MainWindow->show();
			DPInfo(DP_gettext("Could not detect data from any application to import."));
		}
		return(0);
	}
	my %ImportFrom;
	$MainWindow->set_sensitive(0) if($MainWindow);

	# Create the window and vbox
	my $ImportWindow = Gtk2::Window->new();
	$ImportWindow->set_modal(1);
	$ImportWindow->set_transient_for($MainWindow) if $MainWindow;
	$ImportWindow->set_position('center-on-parent');
	$ImportWindow->set_default_size(120,50);
	$ImportWindow->resize(120,50);
	# Do some extra things if it is the first time
	if($FirstTime) {
		$ImportWindow->set_title(DP_gettext("Day Planner") . " - " . DP_gettext("Import data"));
		# Set the icon
		my $WindowIcon = DetectImage("dayplanner_48.png","dayplanner_24.png", "dayplanner_16.png", "dayplanner_HC48.png","dayplanner_HC24.png", "dayplanner_HC16.png", "dayplanner.png");
		if ($WindowIcon) {
			$ImportWindow->set_default_icon_from_file($WindowIcon);
	}	
	} else {
		$ImportWindow->set_title(DP_gettext("Import data"));
		$ImportWindow->set_skip_taskbar_hint(1);
		$ImportWindow->set_skip_pager_hint(1);
	}
	$ImportWindow->set_resizable(0);
	$ImportWindow->set_border_width(12);
	my $ImportVBox = Gtk2::VBox->new();
	$ImportWindow->add($ImportVBox);

	# Create the initial text
	my $LabelText = $FirstTime ? DP_gettext("Welcome to Day Planner.\n\nYou may now choose to import data from other applications.\nCheck the application(s) you want to import from below and\npress Ok to continue, press Cancel if you don't want\nto import anything.") : DP_gettext('Which application(s) do you want to import data from?');
	my $ImportLabel = Gtk2::Label->new($LabelText);
	$ImportLabel->set_line_wrap_mode("word-char");
	$ImportVBox->pack_start($ImportLabel,0,0,0);
	$ImportLabel->show();

	# Evolution
	my $EvolutionButton = Gtk2::CheckButton->new_with_label("Evolution");
	$ImportVBox->pack_start($EvolutionButton,0,0,0);
	$EvolutionButton->show();
	if($Programs{Evolution}) {
		$EvolutionButton->set_active(1);
	} else {
		$EvolutionButton->set_sensitive(0);
	}
	
	# Gnome calendar
	my $GnomeCalButton = Gtk2::CheckButton->new_with_label("Gnome calendar");
	$ImportVBox->pack_start($GnomeCalButton,0,0,0);
	$GnomeCalButton->show();
	if($Programs{GnomeCalendar}) {
		$GnomeCalButton->set_active(1);
	} else {
		$GnomeCalButton->set_sensitive(0);
	}
	
	# Korganizer
	my $KorganizerButton = Gtk2::CheckButton->new_with_label("Korganizer");
	$ImportVBox->pack_start($KorganizerButton,0,0,0);
	$KorganizerButton->show();
	if($Programs{Korganizer}) {
		$KorganizerButton->set_active(1);
	} else {
		$KorganizerButton->set_sensitive(0);
	}
	
	# Plan
	my $PlanButton= Gtk2::CheckButton->new_with_label("Plan");
	$ImportVBox->pack_start($PlanButton,0,0,0);
	if($Programs{Plan}) {
		$PlanButton->set_active(1);
	} else {
		$PlanButton->set_sensitive(0);
	}
	$PlanButton->show();
	
	# Add the buttons
	my $ButtonHBox = Gtk2::HBox->new();
	$ButtonHBox->show();
	$ImportVBox->pack_end($ButtonHBox,0,0,0);
	
	# Ok button
	my $OKButton = Gtk2::Button->new_from_stock('gtk-ok');
	$OKButton->show();
	$OKButton->can_default(1);
	$ImportWindow->set_default($OKButton);
	# Signal callback (starts a function depending on the radio button selected)
	$OKButton->signal_connect("clicked" => sub {
			$ImportWindow->destroy();
			ImportProgData($EvolutionButton->get_active(), $PlanButton->get_active(), $GnomeCalButton->get_active(), $KorganizerButton->get_active());
			$MainWindow->show() if $FirstTime;
		}	# NOTE: Removing this makes perl 5.8.8 in mdv20070 segfault.
			# This might need a bug report
		);
	$ButtonHBox->pack_end($OKButton,0,0,0);

	# Cancel button
	my $CancelButton = Gtk2::Button->new_from_stock('gtk-cancel');
	$CancelButton->show();
	$ButtonHBox->pack_end($CancelButton,0,0,0);
	# Signal callback (destroys the window)
	$CancelButton->signal_connect("clicked" => sub {
			$ImportWindow->destroy();
			$MainWindow->show() if $FirstTime;
			$MainWindow->set_sensitive(1) if $MainWindow;
		});
	$ImportVBox->show();
	$ImportWindow->show();
	return(1);
}

# Purpose: Import data from other programs
# Usage: ImportProgData(EVOLUTION?, PLAN?, GNOME_CAL?, KORGANIZER?);
sub ImportProgData {
	my($ImportEvolution, $ImportPlan, $ImportGnome, $ImportKorganizer) = @_;
	my (@PlanFiles, @EvolutionFiles);

	my $MaxProgress = 1;
	my $CurrentProgress = 1;
	my $Progress = DPCreateProgressWin(DP_gettext("Importing..."), DP_gettext("Preparing"), 0);

	# Get the files to import plan data from
	if($ImportPlan) {
		foreach my $Dir ("$ENV{HOME}/.plan","$ENV{HOME}/.plan.dir") {
			if (defined($Dir) and -d $Dir and -e "$Dir/dayplan") {
				foreach my $File (<$Dir/*>) {
					next if $File =~ m#/(lock\.pland|pland|holiday)$#;
					push(@PlanFiles, $File);
				}
			}
		}
	}

	# Get the files to import Evolution data from
	if($ImportEvolution) {
		my $EvoDir = "$ENV{HOME}/.evolution/calendar/local/system";
		foreach my $File (<$EvoDir/*.ics>) {
			push(@EvolutionFiles, $File);
		}
	}

	# Set MaxProgress for gnome cal
	if($ImportGnome) {
		$MaxProgress++;
	}
	# Set MaxProgress for Korganizer (FIXME: Does it have additional files?)
	if($ImportKorganizer) {
		$MaxProgress++;
	}
	# Set the total MaxProgress
	if(@PlanFiles) {
		$MaxProgress += scalar(@PlanFiles);
	}
	if(@EvolutionFiles) {
		$MaxProgress += scalar(@EvolutionFiles);
	}

	# Begin with gnome
	if($ImportGnome) {
		ProgressMade($MaxProgress, $CurrentProgress, $Progress, "Gnome Calendar");
		$iCalendar->addfile("$ENV{HOME}/.gnome/user-cal.vcf");
		$CurrentProgress++;
	}
	# Continue with evolution
	foreach(@EvolutionFiles) {
		ProgressMade($MaxProgress, $CurrentProgress, $Progress, "Evolution");
		$iCalendar->addfile($_);
		$CurrentProgress++;
	}
	# Then do korganizer
	if($ImportKorganizer) {
		ProgressMade($MaxProgress, $CurrentProgress, $Progress, "Korganizer");
		$iCalendar->addfile("$ENV{HOME}/.kde/share/apps/korganizer/std.ics");
		$CurrentProgress++;
	}
	# End with plan
	foreach(@PlanFiles) {
		ProgressMade($MaxProgress, $CurrentProgress, $Progress, "Plan");
		my %PlanConvertHash;
		if(PlanConvert_ProcessFile($_,\%PlanConvertHash)) {
			PlanConvert_PurgeBuffer(\%PlanConvertHash);
		}
		$CurrentProgress++;
	}
	ProgressMade($MaxProgress, $CurrentProgress, $Progress, DP_gettext("Done"));
	UpdatedData(1);
	$Progress->{Window}->destroy();
	
	$MainWindow->set_sensitive(1) if $MainWindow;
}

# Purpose: Pop up a file picker to find the file to import
# Usage: ImportDataFromFile();
sub ImportDataFromFile {
	# NOTE: We currently rely upon file extensions. This might actually be fine
	# but this note is left as a little heads up. As we filter on filenames anyway
	# the only case would be a file named *.ics which should be *.dpf or the other
	# way around.
	# Create the main window
	my $ImportWindow = Gtk2::FileChooserDialog->new("Import data from file", $MainWindow, 'open',
	'gtk-cancel' => 'reject',
	'gtk-open' => 'accept',);
	$ImportWindow->set_local_only(1);
	$ImportWindow->set_default_response('accept');
	my $filter = Gtk2::FileFilter->new;
	$filter->add_pattern('*.ics');
	$filter->add_pattern('*.vcf');
	$filter->set_name(DP_gettext('All supported file formats'));
	$ImportWindow->add_filter($filter);
	my $Response = $ImportWindow->run();
	if($Response eq 'accept') {
		my $Filename = $ImportWindow->get_filename();
		if($Filename =~ /\.(ics|vcf)$/i) {
			$iCalendar->addfile($Filename);
			UpdatedData();
		} else {
			DPIntWarn("Unknown filetype: $Filename");
		}
	}
	$ImportWindow->destroy();
}

# Purpose: Pop up a graphical dialog for exporting data
# Usage: ExportData();
sub ExportData {
	# Create the main window
	my $ExportWindow = Gtk2::FileChooserDialog->new(DP_gettext("Export data"), $MainWindow, 'save',
	'gtk-cancel' => 'reject',
	'gtk-save' => 'accept',);
	$ExportWindow->set_current_name(DP_gettext("My_dayplanner"));
	$ExportWindow->set_local_only(1);

	# Create the file format selection part
	my $ExportHBox = Gtk2::HBox->new();
	$ExportHBox->show();
	my $ExportVBox = Gtk2::VBox->new();
	$ExportVBox->show();
	$ExportWindow->set_extra_widget($ExportVBox);
	$ExportVBox->pack_start($ExportHBox,0,0,0);

	# Encryption checkbox
	#my $EncryptionCheckbox = Gtk2::CheckButton->new(DP_gettext("Password protect the file (encryption)"));
	#$EncryptionCheckbox->show();
	#$ExportVBox->pack_end($EncryptionCheckbox,0,0,0);
	# Only birthdays checkbox
	#my $BirthdaysOnlyCheckbox = Gtk2::CheckButton->new(DP_gettext("Only export birthdays"));
	#$BirthdaysOnlyCheckbox->show();
	#$ExportVBox->pack_end($BirthdaysOnlyCheckbox,0,0,0);

	# Label
	my $ActiveIndex;
	my $FiletypeLabel = Gtk2::Label->new(DP_gettext("Save as filetype:"));
	$FiletypeLabel->set_justify('left');
	$FiletypeLabel->show();
	$ExportHBox->pack_start($FiletypeLabel,0,0,0);
	# Combo selector
	my $Export_Combo = Gtk2::ComboBox->new_text;
	$Export_Combo->insert_text(0, 'iCalendar (*.ics)');
	$Export_Combo->insert_text(1,         DP_gettext('XHTML (exports to a directory)'));
	$ExportHBox->pack_end($Export_Combo,0,0,0);
	$Export_Combo->show();

	# Handle changed values in the combo box
	$Export_Combo->signal_connect('changed' => sub {
		$ActiveIndex = $Export_Combo->get_active();
		#unless($ActiveIndex == 0) {
		#	$EncryptionCheckbox->set_sensitive(0);
		#} else {
		#	$EncryptionCheckbox->set_sensitive(1);
		#}
		if($ActiveIndex == 1) {
			#$BirthdaysOnlyCheckbox->set_sensitive(0);
			$ExportWindow->set_action('select-folder');
		} else {
			#$BirthdaysOnlyCheckbox->set_sensitive(1);
			$ExportWindow->set_action('save');
		}
	});
	$Export_Combo->set_active(0);

	while (1) {
		my $Response = $ExportWindow->run();
		my $Filename = $ExportWindow->get_filename();
		if($Response eq 'accept') {
			#my $OnlyBirthdays = $BirthdaysOnlyCheckbox->get_active();
			if ($ActiveIndex == 0) {
				if(PromptOverwrite("$Filename.ics")) {
					$iCalendar->write("$Filename.ics");
					last;
				}
			} elsif ($ActiveIndex == 1) {
				if(PromptOverwrite($Filename, 1)) {
					HTML_Export($Filename);
					last;
				}
			} else {
				DPIntWarn("Unknown activeindex: $ActiveIndex");
			}
		} else {
			last;
		}
	}
	$ExportWindow->destroy();
}

# Purpose: Prompt the user to overwrite data
# Usage: PromptOverwrite(PATH, DIR?);
# 	Returns undef on "don't overwrite". Otherwise true.
sub PromptOverwrite {
	my ($File, $Dir) = @_;
	if($Dir) {
		if(-e $File) {
			if(-d $File) {
				foreach(<$File/*>) {
					unless($_ =~ /^\./) {
						if(DPQuestion(sprintf(DP_gettext("There are already files in the directory \"%s\". If you continue any existing exported Day Planner data in that directory will be overwritten. Do you want to continue?"), $File))) {
							return(1);
						} else {
							return(undef);
						}
					}
				}
				return(1);
			} else {
				DPInfo(sprintf(DP_gettext("\"%s\" already exists and is not a directory."), $File));
				return(undef);
			}
		} else {
			return(1);
		}
	} else {
		my $FileDirname = dirname($File);
		unless(-w $FileDirname) {
			DPInfo(sprintf(DP_gettext("\"%s\" is read only. Write permissions are required."), $FileDirname));
			return(undef);
		}
		if(-e $File) {
			if(-d $File) {
				DPInfo(sprintf(DP_gettext("\"%s\" is a directory.")));
				return(undef);
			} elsif(DPQuestion(sprintf(DP_gettext("\"%s\" already exists. Overwrite?"), $File))) {
				return(1);
			} else {
				return(undef);
			}
		} else {
			return(1);
		}
	}
}

# =============================================================================
# GUI CODE
# =============================================================================

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

# Purpose: Initialize gtk2
# Usage: Gtk2Init();
sub Gtk2Init {
	Gtk2->init();
	$Gtk2Init = 1;
}

# Purpose: Create a progresswindow
# Usage: my $ProgressWin = DPCreateProgressWin(WINDOW NAME, INITIAL PROGRESS BAR TEXT, PULSATE_MODE?);
# 	Returns a hashref with the following keys:
# 		Window = The window
# 		ProgressBar = The progress bar
sub DPCreateProgressWin {
	my ($Name, $Text, $PulsateMode) = @_;
	my %ProgressHash;
	$ProgressHash{Window} = Gtk2::Window->new();
	$ProgressHash{Window}->set_skip_taskbar_hint(1);
	$ProgressHash{Window}->set_skip_pager_hint(1);
	if(defined($Name)) {
		$ProgressHash{Window}->set_title($Name);
	}
	if(defined($MainWindow)) {
		$ProgressHash{Window}->set_transient_for($MainWindow);
		$ProgressHash{Window}->set_position('center-on-parent');
	} else {
		$ProgressHash{Window}->set_position('center');
	}
	$ProgressHash{ProgressBar} = Gtk2::ProgressBar->new();
	$ProgressHash{Window}->add($ProgressHash{ProgressBar});
	$ProgressHash{Window}->set_modal(1);
	$ProgressHash{Window}->set_resizable(0);
	if(defined($Text)) {
		$ProgressHash{ProgressBar}->set_text($Text);
	} else {
		$ProgressHash{ProgressBar}->set_fraction(0);
	}
	if($PulsateMode) {
		$ProgressHash{ProgressBar}->{activity_mode} = 0;
	}
	$ProgressHash{ProgressBar}->show();
	$ProgressHash{Window}->show();
	Gtk2->main_iteration while Gtk2->events_pending;
	Gtk2->main_iteration while Gtk2->events_pending;
	Gtk2->main_iteration while Gtk2->events_pending;
	return(\%ProgressHash);
}

# Purpose: Pulsate a progressbar
# Usage: PulsateProgressbar($Progressbar);
sub PulsateProgressbar {
	my $Progressbar = $_[0];
	if(defined($Progressbar)) {		# So that the calling function can just *assume* it has a progressbar
						# even when it doesn't
		$Progressbar->pulse();
		Gtk2->main_iteration while Gtk2->events_pending;
	}
	return(1);
}

# Purpose: Set the progress of a progressbar
# Usage: ProgressMade(FUNCTIONS TO PERFORM, FUNCTIONS PERFORMED, $ProgressWindowHashref, NewText?);
sub ProgressMade {
	return unless $Gtk2Init;

	my ($ToPerform, $Performed, $ProgressHash,$Text) = @_;
	my $Bar = $ProgressHash->{ProgressBar};
	my $Result = sprintf("%d", ($Performed / $ToPerform) * 100);
	if($Result < 100) {
		$Result = "0.$Result"; 
	} else {
		$Result = 1;
	}
	$Bar->set_fraction($Result);
	if($Text) {
		$Bar->set_text($Text);
	}
	Gtk2->main_iteration while Gtk2->events_pending;
	Gtk2->main_iteration while Gtk2->events_pending;
	Gtk2->main_iteration while Gtk2->events_pending;
	return(1, $Result);
}

# Purpose: Set the fraction in a progressbar
# Usage: ProgressFraction($Progressbar, fraction);
sub ProgressFraction {
	my($Progressbar, $fraction) = @_;
	if($Gtk2Init and defined($Progressbar)) {
		$Progressbar->set_fraction($fraction);
		Gtk2->main_iteration while Gtk2->events_pending
	}
}

# Purpose: Populate the upcoming events widget
# Usage: PopulateUpcomingEvents();
sub PopulateUpcomingEvents {
	my $NewUpcoming;
	my $HasUpcoming;
	my %InformationHash;
	my %DayNames = (
		0 => DP_gettext("Sunday"),
		1 => DP_gettext("Monday"),
		2 => DP_gettext("Tuesday"),
		3 => DP_gettext("Wednesday"),
		4 => DP_gettext("Thursday"),
		5 => DP_gettext("Friday"),
		6 => DP_gettext("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}= DP_gettext("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) :";
		my $HasEvents;
		if(my $DateHash = $iCalendar->get_dateinfo($Year+1900, $getmonth, $getmday)) {
			# FIXME: This sort should be so that alphabetical chars come first, then numbers
			# This so that DAY comes before the normal events.
			foreach my $time (sort(@{$iCalendar->get_dateinfo($HumanYear,$getmonth,$getmday)})) {
				foreach my $UID (@{$iCalendar->get_timeinfo($HumanYear,$getmonth,$getmday,$time)}) {
					$HasEvents = 1;
					# If the time is DAY then it lasts the entire day
					if($time eq 'DAY') {
						$h->{text} .= "\n" . GetSummaryString($UID);
					} else {
						$h->{text} .= "\n" . DP_gettext("At") . " " . AMPM_From24($time) . ": " . GetSummaryString($UID);
					}
				}
			}
		}
		unless($HasEvents) {
			# TRANSLATORS: This is used in the upcoming events widget. It is displayed for a day (or set of days)
			#  when no events are present. Ie: Tomorrow (24.02.2007): (nothing).
			$h->{text} .= "\n" . DP_gettext("(nothing)");
			$h->{noevents} = 1;
		} else {
			$HasUpcoming = 1;
		}

	}
	unless($HasUpcoming) {
		$NewUpcoming = DP_gettext("No upcoming events exist for 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} .= " " . DP_gettext("and") . " $InformationHash{$LastDay}{dayname}";
			} else {
				# Build the string
				$InformationHash{$key}{text} .= "$InformationHash{$key}{dayname} " . DP_gettext("and") . " $InformationHash{$LastDay}{dayname}";
			}
			# Delete the $LastDay key
			delete($InformationHash{$LastDay});
			# Finalize the string
			$InformationHash{$key}{text} .= " ($InformationHash{$key}{date}-$LastDate): " . DP_gettext("(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: 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/BirthdayDelete 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 @StandardTypes = ( 'normal', 'allday','bday' );

	if(grep($Type, @StandardTypes)) {
		my ($EventYear, $EventMonth, $EventDay) = $CalendarWidget->get_date();$EventMonth++;
		my $EventUID = $EventlistWidget->{data}[$Selected][0];
		my $EventTime = $EventlistWidget->{data}[$Selected][1];
		my $EventSummary = $EventlistWidget->{data}[$Selected][2];

		# This redraws the event list for us. This is faster
		# than doing the redrawing using the DrawEventlist function.
		my $Array = $EventlistWidget->{data};
		splice(@{$Array},$Selected,1);
		$EventlistWidget->{data} = $Array;
		$ToolbarEditButton->set_sensitive(0);
		$MenuEditEntry->set_sensitive(0);
		$MenuDeleteEntry->set_sensitive(0);
		Gtk2->main_iteration while Gtk2->events_pending;
		$iCalendar->delete($EventUID);

		UpdatedData(0,1);
	} elsif ($Type eq 'holiday') {
		DPInfo(sprintf(DP_gettext("This is a predefined \"holiday\" event. This event can not be edited directly from within Day Planner. In order to change these events use a text editor and edit the file %s."), $HolidayFile));
	} else {
		DPIntWarn("DeleteEvent: Attempted to handle unsupported \$Type: $Type");
	}
}

# 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 $Event;
	if(not $EventlistWidget->{data}[$Selected][0] eq "NULL" and $iCalendar->exists($EventlistWidget->{data}[$Selected][0])) {
		my $Dummy = $EventlistWidget->{data}[$Selected][0];
		$Event = $iCalendar->get_info($EventlistWidget->{data}[$Selected][0]);
	}

	# Detect the event type
	if($Event) {
		if (defined($Event->{'X-DP-BIRTHDAY'}) and $Event->{'X-DP-BIRTHDAY'} eq 'TRUE') {
			return('bday');
		} elsif (not $Event->{DTSTART} =~ /\dT\d/) {
			# Assume all day since there is no T parameter
			return('allday');
		} elsif ($Event->{SUMMARY}) {
			# Assume normal since SUMMARY is set
			return('normal');
		} else {
			DPIntWarn("GetEventListType(): Unknown type of event $EventlistWidget->{data}[$Selected][0]. Dumping data.");
			print Dumper($Event);
		}
	} else {
		return('holiday');
	}
}

# 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 {
	if($Gtk2Init) {
		my $Dialog = Gtk2::MessageDialog->new($MainWindow, "modal", 'error', 'ok', "$_[0]");
		$Dialog->run();
		$Dialog->destroy();
	} else {
		warn($_[0]);
	}
	return(1);
}

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

# Purpose: Display an information dialog (with optional details)
# Usage: DPInfo("Information message", details?);
sub DPInfo {
	if($Gtk2Init) {
		my $Dialog = Gtk2::MessageDialog->new($MainWindow, "modal", 'info', 'ok', "$_[0]");
		if($_[1]) {
			# The expander
			my $FT_Expander = CreateDetailsWidget();
			$Dialog->vbox->add($FT_Expander);
			# The textview field
			my $FulltextView = Gtk2::TextView->new();
			$FulltextView->set_editable(0);
			$FulltextView->set_wrap_mode("word-char");
			$FulltextView->show();
			# Add the text to it
			my $FulltextBuffer = Gtk2::TextBuffer->new();
			$FulltextBuffer->set_text($_[1]);
			$FulltextView->set_buffer($FulltextBuffer);
			# Create a scrollable window to use
			my $FulltextWindow = Gtk2::ScrolledWindow->new;
			$FulltextWindow->set_policy('automatic', 'automatic');
			$FulltextWindow->add($FulltextView);
			$FulltextWindow->show();
			# Add it to the expander
			$FT_Expander->add($FulltextWindow);
		}
		$Dialog->run();
		$Dialog->destroy();
	} else {
		print "$_[0]\n";
		if(defined($_[1])) {
			print "$_[1]\n";
		}
	}
}

# 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->set_sensitive(0);
	DPS_GUIPerform("SEND");
	my $SaveData = SaveMainData();
	if ($SaveData eq 'SAVE_FAILED') {
		unless(DPQuestion(DP_gettext("Some files could not be saved correctly. Quit anyway?"))) {
			$MainWindow->show();
			return(1);
		}
	} elsif ($SaveData eq 'DAEMON_RELOAD_FAILURE') {
		unless(DPQuestion(DP_gettext("Quit anyway?"))) {
			$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
	my $MonthInfo = $iCalendar->get_monthinfo(@_);
	if ($MonthInfo) {
		foreach my $Day (@{$MonthInfo}) {
			$CalendarWidget->mark_day($Day);	# Mark this day
		}
	}
}

# Purpose: The same as localtime(TIME?); but returns proper years and months
# Usage: my ($currsec,$currmin,$currhour,$currmday,$currmonth,$curryear,$currwday,$curryday,$currisdst) = GetDate(TIME);
#  TIME is optional. If not present then the builtin time function is called.
sub GetDate {
	my $Time = $_[0] ? $_[0] : time;
	my ($currsec,$currmin,$currhour,$currmday,$currmonth,$curryear,$currwday,$curryday,$currisdst) = localtime($Time);
	$curryear += 1900;						# Fix the year format
	$currmonth++;							# Fix the month format
	return($currsec,$currmin,$currhour,$currmday,$currmonth,$curryear,$currwday,$curryday,$currisdst);
}

# 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: Create the details widget
# Usage: my $ExpanderWidget = CreateDetailsWidget();
sub CreateDetailsWidget {
	my $FT_Expander = Gtk2::Expander->new(DP_gettext("Show details"));
	$FT_Expander->show();
	$FT_Expander->signal_connect("activate" => sub {
			# Yes, it is weird to use not here, but in the callback it appears
			# to return "" if it is expanded and 1 if it isn't.
			# Possibly a race condition within gtk2. This appears to work anyway.
			if(not $FT_Expander->get_expanded) {
				$FT_Expander->set_label(DP_gettext('Hide details'));
			} else {
				$FT_Expander->set_label(DP_gettext('Show details'));
			}
		});
	return($FT_Expander);
}

# Purpose: Get a properly formatted event time from two widgets (min/hour)
# Usage: my $Time = GetTimeFromWidgets($HourSpinner, $MinuteSpinner, $AMPM);
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, $AMPM) = @_;	
	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";
	}
	if(defined($AMPM)) {
		my $Prefix;
		if($AMPM->get_active() == 0) {
			$Prefix = $AM_String;
		} else {
			$Prefix = $PM_String;
		}
		($Hour,$Minute) = AMPM_To24("$Hour:$Minute $Prefix", 1);
	}
	return("$Hour:$Minute");
}

# Purpose: Create the widgets for selecting the time
# Usage: my ($HourSpinner, $MinuteSpinner, $TimeHBox, $AMPM) = TimeSelection("HH:MM");
sub TimeSelection {
	my $Time = $_[0];
	my($HourAdjustment,$MinuteAdjustment, $HourSpinner,$MinuteSpinner,$AMPM);

	if($ClockSystem == 24) {
		# The hour adjustment
		$HourAdjustment = Gtk2::Adjustment->new(0.0, 0.0, 23.0, 1.0, 5.0, 0.0);
	} else {
		# The hour adjustment
		$HourAdjustment = Gtk2::Adjustment->new(0.0, 1.0, 12.0, 1.0, 5.0, 0.0);
	}
	# The minute adjustment
	$MinuteAdjustment = Gtk2::Adjustment->new(0.0, 0.0, 59.0, 1.0, 5.0, 0.0);
	# Create the spinners
	$HourSpinner = Gtk2::SpinButton->new($HourAdjustment, 0, 0);
	$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);
	
	if($ClockSystem == 24) {
		# 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);
	} else {
		# Get the time
		my $HSTime = AMPM_From24($Time);
		my $MSTime = AMPM_From24($Time);
		my $Suffix = AMPM_From24($Time);
		$HSTime =~ s/^(\d+):.*$/$1/;
		$MSTime =~ s/^\d+:(\d+).*$/$1/;
		$Suffix =~ s/^\d+:\d+\s+(.+)/$1/;
		# Set the initial value
		$HourSpinner->set_value($HSTime);
		$MinuteSpinner->set_value($MSTime);
		# Create the combo box
		$AMPM = Gtk2::ComboBox->new_text;
		$AMPM->insert_text(0, $AM_String);
		$AMPM->insert_text(1, $PM_String);
		$AMPM->show();
		# Set the initial value
		if($Suffix eq $AM_String) {
			$AMPM->set_active(0);
		} else {
			$AMPM->set_active(1);
		}
	}
	
	# 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);
	if(defined($AMPM)) {
		$TimeSpinnerHBox->pack_start($AMPM,0,0,0);
	}
	$TimeSpinnerHBox->show();

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

# 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(DP_gettext("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(DP_gettext("Add an event"), DP_gettext('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(DP_gettext("Event type:"));
	$SelectorHBox->pack_start($EventTypeLabel,0,0,0);
	$EventTypeLabel->show();
	# Combo selector
	my $EventType_Combo = Gtk2::ComboBox->new_text;
	# TRANSLATORS: Context: Event type: THIS STRING
	$EventType_Combo->insert_text(0, DP_gettext("Normal"));
	# TRANSLATORS: Context: Event type: THIS STRING
	$EventType_Combo->insert_text(1, DP_gettext("All day"));
	# TRANSLATORS: Context: Event type: THIS STRING
	$EventType_Combo->insert_text(2, DP_gettext("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, DP_gettext("Add this event"));
	$Tooltips->set_tip($CancelButton, DP_gettext("Discard this event"));
	
	# Create the widgets for the normal event selection
	my ($NE_MainWidget, $NE_HourSpinner, $NE_MinuteSpinner, $NE_AMPM, $NE_SummaryWidget, $NE_DetailsWidget) = NormalEventWindow("NULL", $OKButton, $VBox_HBoxContainer, $Window);
	# Create the widgets for the all day event selection
	my ($AE_MainWidget, $AE_SummaryWidget, $AE_DetailsWidget) = AllDayEventWindow("NULL", $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();
				$AE_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_AMPM, $NE_SummaryWidget, $NE_DetailsWidget, \%TimeHash, 0);
				});
			} elsif ($ActiveIndex == 1) {
				$NE_MainWidget->hide();
				$BE_MainWidget->hide();
				$AE_MainWidget->show();
				$OKButton->signal_handler_disconnect($OKSignal) if defined($OKSignal);
				$OKSignal = $OKButton->signal_connect('clicked' => sub {
					AllDayEvent_OK($Window, $AE_SummaryWidget, $AE_DetailsWidget, \%TimeHash, 0)});
			} elsif ($ActiveIndex == 2) {
				$NE_MainWidget->hide();
				$AE_MainWidget->hide();
				$BE_MainWidget->show();
				$OKButton->signal_handler_disconnect($OKSignal) if defined($OKSignal);
				$OKSignal = $OKButton->signal_connect('clicked' => sub {
					AllDayEvent_OK($Window, $BE_NameWidget, undef, \%TimeHash, 0,1)});
			}
		});

	$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 $EventUID = $EventlistWidget->{data}[$Selected][0];
	my $Time = AMPM_To24($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
		# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		
		($Window, $VBox_HBoxContainer, $OKButton, $CancelButton) = CreateEventContainerWin(DP_gettext("Editing an event"), DP_gettext('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_AMPM, $NE_SummaryWidget, $NE_DetailsWidget) = NormalEventWindow($EventUID, $OKButton, $VBox_HBoxContainer, $Window);
		$OKButton->signal_connect('clicked' => sub {
				NormalEvent_OK($Window, $NE_HourSpinner, $NE_MinuteSpinner, $NE_AMPM, $NE_SummaryWidget, $NE_DetailsWidget, \%TimeHash, $EventUID);
		});
		
		# Show the default widget (Normal event)
		$NE_MainWidget->show();
	} elsif ($Type eq 'bday') {
		# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		# BIRTHDAY EVENT
		# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		($Window, $VBox_HBoxContainer, $OKButton, $CancelButton) = CreateEventContainerWin(DP_gettext("Editing a birthday"), DP_gettext('Editing a birthday on the %s. %s %s'), 'gtk-ok');
		
		my ($BE_MainWidget, $BE_NameWidget) = BirthdayEventWindow($EventUID, $VBox_HBoxContainer, $Window);
		$BE_MainWidget->show();
		$OKButton->signal_connect('clicked' => sub { AllDayEvent_OK($Window, $BE_NameWidget, undef, \%TimeHash, $EventUID, 1)});
	} elsif ($Type eq 'allday') {
		# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		# ALL DAY EVENT
		# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		($Window, $VBox_HBoxContainer, $OKButton, $CancelButton) = CreateEventContainerWin(DP_gettext("Editing an all day event"), DP_gettext('Editing an all day event on the %s. %s %s'), 'gtk-ok');
		my ($AD_MainWidget, $AD_SummaryWidget, $AD_DetailsWidget) = AllDayEventWindow($EventUID, $VBox_HBoxContainer, $Window);
		$AD_MainWidget->show();
		$OKButton->signal_connect('clicked' => sub { AllDayEvent_OK($Window, $AD_SummaryWidget, $AD_DetailsWidget, \%TimeHash, $EventUID, 0)});
	} elsif ($Type eq 'holiday') {
		DPInfo(sprintf(DP_gettext("This is a predefined \"holiday\" event. This event can not be edited directly from within Day Planner. In order to change these events use a text editor and edit the file %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, DP_gettext("Discard changes"));
	$Tooltips->set_tip($OKButton, DP_gettext("Accept changes"));
			
	# Show the window
	$Window->show();
}

# Purpose: Create the widgets for editing a normal event
# Usage: my ($NormalEventWidget, $HourSpinner, $MinuteSpinner, $SummaryWidget, $DetailsWidget) = NormalEventWindow(UID, OK_BUTTON, VBOX_WIDGET, MAIN_WINDOW_WIDGET);
#
# UID is the event UID of the event being edited 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 ($EventUID, $OKButton, $ParentVBox, $MyWindow) = @_;

	my $IsEditing = 0;
	
	my ($EventYear, $EventMonth, $EventDay) = $CalendarWidget->get_date();$EventMonth++;
	my ($EventSummary, $EventFulltext,$EventTime);
	my ($OldEventYear, $OldEventMonth,$OldEventDay,$OldEventTime);
	
	# Check if the event is already defined, if it is then we assume that we're editing
	if($EventUID eq "NULL") {
		$EventTime = "00:00";
	} else {
		my $EventHash = $iCalendar->get_info($EventUID);
		# We assume these two aren't empty. This will need to change if another part becomes obligatory
		my ($Year, $Month, $Day, $Time) = iCal_ParseDateTime($EventHash->{DTSTART});
		$EventTime = $Time;
		$EventSummary = GetSummaryString($EventUID);
		$EventFulltext = $EventHash->{DESCRIPTION};
		($OldEventYear, $OldEventMonth,$OldEventDay,$OldEventTime) = ($EventYear,$EventMonth,$EventDay,$EventTime);
		$IsEditing = 1;
	}

	# 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(DP_gettext('Time:'));
	$TimeLabel->show();
	$ContentTable->attach_defaults($TimeLabel, 0,1,0,1);
	
	# Time entry box
	my ($HourSpinner, $MinuteSpinner, $TimeHBox, $AMPM) = 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(DP_gettext('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, DP_gettext("Enter a description of the event here"));
	
	# ==================================================================
	# ADD THE EXTENDED ENTRY
	# ==================================================================
	my $FulltextWindow;
	
	my $FT_Expander = CreateDetailsWidget();
	if ($UserConfig{EditorVerboseDefault}) {
		$FT_Expander->set_expanded(1);
	}
	$Tooltips->set_tip($FT_Expander, DP_gettext("Additional information"));
	$MainVBox->pack_start($FT_Expander,0,0,0);
	
	# 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, $AMPM, $SummaryEntry, $FulltextEntry);
}

# Purpose: Create the widgets for editing an all-day event
# Usage: my ($AllDayEventWidget, $SummaryWidget, $DetailsWidget) = AllDayEventWindow(UID, VBOX_WIDGET, MAIN_WINDOW_WIDGET);
#
# UID is the UID of the event you're editing or undef
# VBOX_WIDGET is the widget you want to pack our table into
# MAIN_WINDOW_WIDGET is the main window it will live inside
#
# TODO: Make AllDayEventWindow and NormalEventWindow share the same
# summary and fulltext entries!
sub AllDayEventWindow {
	# ==================================================================
	# INITIALIZE
	# ==================================================================
	my ($EventUID, $ParentVBox, $MyWindow) = @_;

	my ($EventYear, $EventMonth, $EventDay) = $CalendarWidget->get_date();$EventMonth++;
	my ($EventSummary, $EventFulltext,$EventTime);
	my ($OldEventYear, $OldEventMonth,$OldEventDay,$OldEventTime);
	
	# Check if the event is already defined, if it is then we assume that we're editing
	if(not $EventUID eq "NULL") {
		my $EventHash = $iCalendar->get_info($EventUID);
		# We assume these two aren't empty. This will need to change if another part becomes obligatory
		my ($Year, $Month, $Day) = iCal_ParseDateTime($EventHash->{DTSTART});
		$EventSummary = GetSummaryString($EventUID);
		$EventFulltext = $EventHash->{DESCRIPTION};
		($OldEventYear, $OldEventMonth,$OldEventDay) = ($EventYear,$EventMonth,$EventDay);
	}

	# 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 SUMMARY BOX
	# ==================================================================
	
	# Create the summary label
	my $SummaryLabel = Gtk2::Label->new(DP_gettext('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, DP_gettext("Enter a description of the event here"));
	
	# ==================================================================
	# ADD THE EXTENDED ENTRY
	# ==================================================================
	my $FulltextWindow;
	
	my $FT_Expander = CreateDetailsWidget();
	if ($UserConfig{EditorVerboseDefault}) {
		$FT_Expander->set_expanded(1);
	}
	$Tooltips->set_tip($FT_Expander, DP_gettext("Additional information"));
	$MainVBox->pack_start($FT_Expander,0,0,0);
	
	# 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,$SummaryEntry, $FulltextEntry);
}

# Purpose: Create the widgets for editing a birthday(birthdays.dpd) event (%BirthdayContents)
# Usage: my ($BirthdayEventWidget, $SummaryWidget) = NormalEventWindow(UID, VBOX_WIDGET, MAIN_WINDOW_WIDGET);
#
# UID is the event UID of the event being edited 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 ($UID, $ParentVBox, $MyWindow) = @_;
	my ($EventYear, $EventMonth, $EventDay) = $CalendarWidget->get_date();$EventMonth++;
	
	# 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(DP_gettext('Name:'));
	$SummaryLabel->show();
	$ContentTable->attach_defaults($SummaryLabel, 0,1,1,2);

	# Create the summary entry box
	my $SummaryEntry = Gtk2::Entry->new();
	if (defined($UID) and length($UID)) {
		my $Info = $iCalendar->get_info($UID);
		$SummaryEntry->set_text($Info->{'X-DP-BIRTHDAYNAME'});
	}
	$SummaryEntry->set_activates_default(1);
	$SummaryEntry->show();
	$ContentTable->attach_defaults($SummaryEntry, 1,2,1,2);
	$Tooltips->set_tip($SummaryEntry, DP_gettext("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, EventUID)});
#
# EventUID can be undef.
sub NormalEvent_OK {
	my ($MyWindow, $HourSpinner, $MinuteSpinner, $AMPM, $SummaryEntry, $FulltextEntry, $TimeHash, $EventUID) = @_;
	my $Error = 0;
	
	# Get the contents
	my $Time = GetTimeFromWidgets($HourSpinner,$MinuteSpinner, $AMPM);
	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};
	
	# TODO: Add tests for an event that is *identical*
	unless ($Summary) {
		DPError(DP_gettext("There is no summary for this event. Please enter a summary."));
		$Error = 1;
	}
	# We don't do this if an error occurred
	unless ($Error) {
		my %EntryHash;
		# Add them to the hash
		$EntryHash{SUMMARY} = $Summary;
		$EntryHash{DESCRIPTION} = $Fulltext;
		$EntryHash{DTSTART} = iCal_GenDateTime($EventYear, $EventMonth, $EventDay, $Time);
		$EntryHash{DTEND} = iCal_GenDateTime($EventYear, $EventMonth, $EventDay, $Time);
		if($EventUID) {
			$iCalendar->change($EventUID,%EntryHash);
		} else {
			$iCalendar->add(%EntryHash);
		}
		
		# 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 all day event widget and birthday event widget
# Usage: $OKButton->signal_connect('clicked' => sub { AllDayEvent_OK($MainWindow, $SummaryEntry, $FulltextEntry, \%TimeHash, EventUID, IS_BIRTHDAY)});
#
# EventUID can be empty. If empty then add-mode is used.
#
# IS_BIRTHDAY can be either true or false. If the entry being changed/added is a birthday
# then it should be true.
sub AllDayEvent_OK {
	my ($MyWindow, $SummaryEntry, $FulltextEntry, $TimeHash, $EventUID,$IsBirthday) = @_;
	my $Error = 0;
	
	# Get the contents of the summary
	my $Summary = $SummaryEntry->get_text;

	# 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};
	
	# TODO: Add tests for an event that is *identical* (wouldn't this be a job for DP::iCalendar ?)
	unless ($Summary) {
		DPError(DP_gettext("There is no summary for this event. Please enter a summary."));
		$Error = 1;
	}
	# We don't do this if an error occurred
	unless ($Error) {
		my %EntryHash;
		# Add them to the hash
		$EntryHash{DTSTART} = iCal_GenDateTime($EventYear, $EventMonth, $EventDay);
		$EntryHash{DTEND} = iCal_GenDateTime($EventYear, $EventMonth, $EventDay);
		if($IsBirthday) {
			$EntryHash{'X-DP-BIRTHDAY'} = 'TRUE';
			$EntryHash{'X-DP-BIRTHDAYNAME'} = $Summary;
			$EntryHash{RRULE} = 'FREQ=YEARLY';
			$EntryHash{SUMMARY} = sprintf(DP_gettext("%s's birthday"), $Summary);
		} else {
			$EntryHash{SUMMARY} = $Summary;
			# Get and add content to the fulltext
			my $FulltextBuff = $FulltextEntry->get_buffer;
			my $Fulltext = $FulltextBuff->get_text($FulltextBuff->get_bounds,1);
			$EntryHash{DESCRIPTION} = $Fulltext;
		}
		if($EventUID) {
			$iCalendar->change($EventUID,%EntryHash);
		} else {
			$iCalendar->add(%EntryHash);
		}
		
		# 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);
	}
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Main 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, 2007");
	$AboutDialog->set_website("http://www.day-planner.org/");
	$AboutDialog->set_name(DP_gettext("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 (DP_gettext("THE NAMES OF THE TRANSLATORS") eq "THE NAMES OF THE TRANSLATORS") {
		$AboutDialog->set_translator_credits(DP_gettext("THE NAMES OF THE TRANSLATORS"));
	}
	$AboutDialog->run;
	$AboutDialog->destroy();
	$MainWindow->set_sensitive(1);
}

# Purpose: Draw the preferences window and allow the user to set different
#  configuration options
# Usage: PreferencesWindow(NAME);
sub PreferencesWindow {
	$_[0] =~ s/_//;
	my $Name = $_[0];
	$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($Name);
	$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);
			WriteConfig();
			});
	
	# 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>" . DP_gettext("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(DP_gettext("Show details by default"));
	$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, DP_gettext("Check this if you want to show the details by default in the add and edit event dialog."));

	# ==================================================================
	# 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>" . DP_gettext("Event notifications") . "</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(DP_gettext("Start the Day Planner reminder automatically when logging in"));
	$AutostartReminder_Checkbox->show();
	$Events_VBox->pack_start($AutostartReminder_Checkbox,0,0,0);
	$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(DP_gettext("Use notifications before events"));
	$Events_EnabledCheckbox->show();
	$Events_VBox->pack_start($Events_EnabledCheckbox,0,0,0);

	# 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(DP_gettext('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(DP_gettext("%s minutes"),"10"));
	$TimeSel_Combo->insert_text(1, sprintf(DP_gettext("%s minutes"),"20"));
	$TimeSel_Combo->insert_text(2, sprintf(DP_gettext("%s minutes"),"30"));
	$TimeSel_Combo->insert_text(3, sprintf(DP_gettext("%s minutes"),"45"));
	$TimeSel_Combo->insert_text(4, sprintf(DP_gettext("%s hour"),"1"));
	$TimeSel_Combo->insert_text(5, sprintf(DP_gettext("%s hours"),"2"));
	$TimeSel_Combo->insert_text(6, sprintf(DP_gettext("%s hours"),"4"));
	$TimeSel_Combo->insert_text(7, sprintf(DP_gettext("%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(DP_gettext("Remind 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);
	}
	
	# 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: Populate the event list for the currently selected date in the calendar
# Usage: PopulateEventList();
sub PopulateEventList {
	@{$EventlistWidget->{data}} = ();
	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);
	
	# Main calendar contents
	if (my $TimeArray = $iCalendar->get_dateinfo($Year,$Month,$Day)) {
		foreach my $Time (sort @{$TimeArray}) {
			foreach my $UID (@{$iCalendar->get_timeinfo($Year,$Month,$Day,$Time)}) {
				my $EventSummary = GetSummaryString($UID);
				# Don't add Time = DAY.
				if($Time eq 'DAY') {
					$Time = '';
				}
				push (@{$EventlistWidget->{data}}, [$UID,AMPM_From24($Time), $EventSummary]);
			}
		}
	}
	# Fetch the birthdays
#	if (defined($BirthdayContents{$Month}) and defined($BirthdayContents{$Month}{$Day})) {
#		foreach my $Birthday (sort keys %{$BirthdayContents{$Month}{$Day}}) {
#			push (@{$EventlistWidget->{data}}, [0,"", sprintf(DP_gettext("%s's birthday"), $Birthday)]);
#		}
#	}
	# Fetch the Holidays if the year is above 1970 (UNIX epoch) and 2037 (UNIX epoch overflow)
	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}}, [0,"", $CurrHoliday]);
				}
			}
		}

	}
	$EventlistWidget->show();
}

# Purpose: Draw the eventlist on the currently selected date in the calendar
# Usage: DrawEventlist();
sub DrawEventlist {
	# Greate the pop-up on right-click
	my $PopupWidget = Gtk2::Menu->new();
	my $delete = Gtk2::ImageMenuItem->new_from_stock('gtk-delete');
	$delete->show();
	$delete->signal_connect('activate' => \&DeleteEvent);
	my $edit = Gtk2::ImageMenuItem->new_from_stock('gtk-edit');
	$edit->show();
	$edit->signal_connect('activate' => \&EditEvent);
	$PopupWidget->append($edit);
	$PopupWidget->append($delete);
	$PopupWidget->show();

	# Create the widget and set up signal handlers
	$EventlistWidget = Gtk2::SimpleList->new (
		'UID' => 'hidden',
		DP_gettext("Time") => 'text',
		DP_gettext("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 {
		# 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);
		}
	});
	$EventlistWidget->signal_connect('button-release-event' => sub {
			my($widget,$event) = @_;
			my $button = $event->button;
			if($button == 3) {
				my($self, $event) = @_;
				my ($path, $column, $cell_x, $cell_y) = $EventlistWidget->get_path_at_pos ($event->x, $event->y);
				if(scalar($widget->get_selected_indices) > 0) {
					if(defined($path)) {
						$PopupWidget->popup(undef, undef, undef, undef, 0, 0);
					}
				}
			}
		});
	PopulateEventList();
}

# 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(DP_gettext("Day Planner"));
	$MainWindow->set_default_size ($InternalConfig{MainWin_Width},$InternalConfig{MainWin_Height});
	$MainWindow->maximize if ($InternalConfig{MainWin_Maximized});
	if($InternalConfig{MainWin_X} and $InternalConfig{MainWin_Y}) {
		$MainWindow->move($InternalConfig{MainWin_X}, $InternalConfig{MainWin_Y});
	}

	# 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
	# ==================================================================
	
	# Get stock values
	my $EditStock = Gtk2::Stock->lookup('gtk-edit')->{label};
	my $QuitStock = Gtk2::Stock->lookup('gtk-quit')->{label};
	my $PrefsStock = Gtk2::Stock->lookup('gtk-preferences')->{label};
	my $AboutStock = Gtk2::Stock->lookup('gtk-about')->{label};
	my $HelpStock = Gtk2::Stock->lookup('gtk-help')->{label};
	my $DeleteStock = Gtk2::Stock->lookup('gtk-delete')->{label};

	# The menu items
	my @MenuItems = (
		# Calendar menu
		[ "/" . DP_gettext("_Calendar"),						undef,			undef,			0,	"<Branch>"],
		[ "/" . DP_gettext("_Calendar") . "/" . DP_gettext("_Import"),		"",		\&ImportData,		1,	"<StockItem>",	'gtk-open'],
		[ "/" . DP_gettext("_Calendar") . "/" . DP_gettext("_Export"),		undef,		\&ExportData,		1,	"<StockItem>",	'gtk-save-as'],
		[ "/" . DP_gettext("_Calendar") . "/sep",					undef,			undef,			2,	'<Separator>'],
		[ "/" . DP_gettext("_Calendar") . "/$QuitStock",		"<control>Q",		\&QuitSub,		3,	"<StockItem>",	'gtk-quit'],
		# Edit menu
		[ "/$EditStock", undef,			undef,			0,	"<Branch>"],
		[ "/$EditStock/" . DP_gettext("_Add an event"),		"<control>A",		\&AddEvent,		1,	'<StockItem>',	'gtk-add' ],
		[ "/$EditStock/" . DP_gettext("_Edit this event"),	"<control>E",		\&EditEvent,		2,	'<StockItem>',	'gtk-edit' ],
		[ "/$EditStock/" . DP_gettext("_Delete this event"),	"<control>D",		\&DeleteEvent,		3,	'<StockItem>',	'gtk-delete' ],
		[ "/$EditStock/sep",					undef,			undef,			4,	'<Separator>'],
		[ "/$EditStock/$PrefsStock" , undef,			sub { PreferencesWindow($PrefsStock) },		1, 	'<StockItem>', 'gtk-preferences' ],
		# Help menu
		[ "/$HelpStock",						undef,			undef,			0,	"<Branch>" ],
		[ "/$HelpStock/$AboutStock" ,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 = "/$EditStock/" . DP_gettext("_Edit this event");
	$Get =~ s/_//g;
	$MenuEditEntry = $Menu_ItemFactory->get_widget($Get);

	$Get = "/$EditStock/" . DP_gettext("_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
	# Get the current time
	my ($currsec,$currmin,$currhour,$currmday,$currmonth,$curryear,$currwday,$curryday,$currisdst) = GetDate();
	# Create the calendar
	$CalendarWidget = Gtk2::Calendar->new;
	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);
			PopulateEventList();
		});

	# 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);
	
	# ==================================================================
	# EVENT 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();
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Initialization
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Initialize the main program
# Usage: Gtk2->init_add(\&InitDPCore);
sub InitDPCore {
	DP_InitI18n();
	my $Exception =  Glib->install_exception_handler(sub {
		DPError("An exception occurred. This reflects a bug in the application.\nPlease save the following exception text and report this problem to the Day Planner developers.\nNow some things may not work as expected\n\nException:\n $_[0]");
		1;
	});
	
	# MainInit() returns 1 if we're in "FirstStartup" mode AND are displaying the import window.
	# If we're not displaying the import window it returns 0 in any case.
	my $FirstStartup = MainInit();
	# Draw the main window
	DrawMainWindow();
	$MainWindow->show() if not $FirstStartup;
	# Initialize the daemon
	DaemonInit() or die();
	# Connect to a DPS server and sync the local data with it if it's enabled
	DPS_GUIPerform("GET");
	# 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();
}

# 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("-s", "--shutdaemon", "Shutdown the daemon when Day Planner is closed");
		PrintHelp("", "--exportical", "Export the Day Planner data in the iCalendar format");
		PrintHelp("", "", "to the filename supplied");
		PrintHelp("", "--exporthtml", "Export the Day Planner data in the HTML format to the");
		PrintHelp("", "", "directory supplied");
		PrintHelp("", "--importical", "Import data from the iCalendar file specified");
		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";
		if(eval("use Locale::gettext;1")) {
			print "Locale::gettext version: $Locale::gettext::VERSION\n";
		} else {
			print "Locale::gettext version: MISSING\n";
		}
		printf "Perl version %vd\n", $^V;
		print "OS: ", GetDistVer(), "\n";
		# Display module information (also includes daemon and notifier deps)
		my @AvailModules;
		my @UnavailModules;
		foreach(sort qw/Locale::gettext POSIX Gtk2 Data::Dumper Gtk2::SimpleList Gtk2::Gdk::Keysyms Getopt::Long Cwd File::Basename IO::Socket File::Copy FindBin MIME::Base64 Digest::MD5 File::Path Date::HolidayParser IO::Select DP::iCalendar/) {
			if(eval("use $_; 1")) {
				push(@AvailModules, $_);
			} else {
				push(@UnavailModules, $_);
			}
		}
		print "Available modules:";
		print " $_" foreach(@AvailModules);
		print " (none)" unless(@AvailModules);
		print "\nUnavailable modules:";
		print " $_" foreach(@UnavailModules);
		print " (none)" unless(@UnavailModules);
		print "\n";
		exit(0);
	},
	'test|t:i' => sub {
		my $NumberPrefix = $_[1] ? $_[1] : "";
		DPIntInfo("Running in test mode (using ~/.dayplanner/debug$NumberPrefix)");
		$SaveToDir = "$ENV{HOME}/.dayplanner/debug$NumberPrefix";
	},
	'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];
	},
	's|shutdaemon' => \$ShutdownDaemon,
	# The export functions that call CLI_Export() needs to be seperate because
	# CLI_Export does some magic on the commandline parameter name and gets
	# confused when it gets all the options available.
	'exportical|exportics=s' => \&CLI_Export,
	'exportics_dps=s' => sub {
		$DPServices{StaticUID} = 1;
		CLI_Export(@_);
		$DPServices{StaticUID} = 0;
	},
	'exporthtml=s' => \&CLI_Export,
	'exportphp=s' => \&CLI_Export,
	'importical|importics=s' => \&CLI_Import,
) 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}) {
	Gtk2Init();
	DPError(sprintf(DP_gettext("The environment variable %s is not set! Unable to continue\n"), "HOME"));
	die(sprintf(DP_gettext("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}) {
	DP_InitI18n();
	printf(DP_gettext("The home directory of the user %s doesn't exist at %s! Please verify that the environment variable %s is properly set. Unable to continue\n"),[getpwuid($<)]->[0], $ENV{HOME}, "HOME");
	Gtk2Init();
	DPError(sprintf(DP_gettext("The home directory of the user %s doesn't exist at %s! Please verify that the environment variable %s is properly set. Unable to continue\n"),[getpwuid($<)]->[0], $ENV{HOME}, "HOME"));
	die("\n");
}

Gtk2Init();
Gtk2->init_add(\&InitDPCore);
# Rest in the Gtk2 main loop
Gtk2->main;
