#!/usr/bin/perl
# Day Planner
# A graphical Day Planner written in perl that uses Gtk2
# Copyright (C) Eskild Hustvedt 2006, 2007
# $Id: dayplanner 1334 2007-04-11 19:30: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 Glib qw(TRUE FALSE);	# We need glibs TRUE and FALSE, they prettify code
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::UNIX;		# 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.6';
my $RCSRev = '$Id: dayplanner 1334 2007-04-11 19:30: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 = '06';				# 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.



# FIXME: Unused
# 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,	$ToolbarDeleteButton,
);	# 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;
			if($BindTo) {
				bindtextdomain('dayplanner', $BindTo);
			}
			textdomain('dayplanner');
			# This is a hack, but works in many cases
			bind_textdomain_codeset('dayplanner','ISO-8859-15');
		} else {
			$DP_I18N_Mode = 2;
			# The reason we use domain_raw and sets the ->codeset is because for some reason
			# in later Gtk2 perls (or possibly buggy Locale::gettexts) special characters
			# doesn't get properly returned.
			$Gettext = Locale::gettext->domain_raw('dayplanner');	# Set the gettext domain
			$Gettext->codeset('UTF-8');
			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 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()
	if(defined($ENV{DP_FORCE_24H}) and $ENV{DP_FORCE_24H}) {
		$ClockSystem = '24';
	} else {
		$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::SSL 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)
#		TIMEOUT = The connection timed out
#		* = Anything else simply returns $@
sub IO_Socket_INET_Errors {
	my $Error = shift;
	if($Error =~ /Network is unreachable/i) {
		return('OFFLINE');
	} elsif ($Error =~ /Bad hostname/i) {
		return('BADHOST');
	} elsif ($Error =~ /Connection refused/i) {
		return('REFUSED');
	} elsif ($Error =~ /timeout/i) {
		return('TIMEOUT');
	} else {
		return($Error);
	}
}

# 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, it is optional.
#	If no technical_error is supplied then User_error is used.
sub DPS_Error {
	my $user_error = shift;
	# Tech_error is set to user_error when not supplied
	my $tech_error = $_[0] ? $_[0] : $user_error;
	DPIntWarn("DPS: $tech_error");
	DPS_Log($tech_error);
	if(defined($user_error)) {
		$DPServices{Error} = $user_error;
	}
}

# Purpose: Set the status in the DPS GUI Window
# Usage: DPS_Status(TEXT, COMPLETED);
#	COMPLETED is a number between 0 and 1 (such as 0.1, 0.5)
#	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: Upload data to a Day Planner services server
# Usage: DPS_Upload();
sub DPS_Upload {
	my $LastMD5 = $InternalConfig{DPS_LastMD5} ? $InternalConfig{DPS_LastMD5} : "undef";
	my $SendData = encode_base64($iCalendar->get_rawdata(),'');
	chomp($SendData);
	my $MD5 = md5_base64($SendData);
	my $Reply = DPS_DataSegment("SENDDATA $MD5 $LastMD5 $SendData 0");
	if(not $Reply eq 'OK') {
		# TODO: These need cleaning
		if($Reply =~ s/^ERR\s+(.*)$/$1/) {
			DPS_Error(DP_gettext('An error ocurred while uploading 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));
		}
		return(undef);
	}
	# We successfully uploaded the data. So set DPS_LastMD5 and return true
	$InternalConfig{DPS_LastMD5} = $MD5;
	return(1);
}

# Purpose: Download data from a Day Planner services server
# Usage: DPS_Download(MERGE?);
#	If MERGE is true then it will not overwrite the current data
#	with the downloaded data, but rather use the DP::iCalendar merge
#	function to merge it into place.
# This function itself is stupid, it doesn't know about MD5 sums of local data
# or anything. It will download the data no matter what, it is up to the caller
# to check if we actually need to download data or not.
sub DPS_Download {
	my $Merge = shift;
	my $Data = DPS_DataSegment('GETDATA');
	if($Data =~ /^OK/) {
		DPS_Log('Downloaded data');
		my $Initial = $Data;
		my $MD5 = $Data;
		my $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/) {
			# FIXME: Rewrite this
			DPS_Error("FATAL: UNABLE TO GRAB DATA. DUMPING DATA:\nData recieved: $Initial");
		}
		elsif(not md5_base64($MainData) eq $MD5) {
			# FIXME: Rewrite this
			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 {
			# Decode the base64 encoded data
			$MainData = decode_base64($MainData);
			# Remove junk and populate @DataArray
			my @DataArray;
			$MainData =~ s/\r//g;
			push(@DataArray, $_) foreach(split(/\n/,$MainData));
			$MainData = undef;
			# If we're in merge mode then enable SMART MERGE and then add
			# the file, if not then clean and add the file
			if($Merge) {
				$iCalendar->enable('SMART_MERGE');
				$iCalendar->addfile(\@DataArray);
				$iCalendar->disable('SMART_MERGE');
			} else {
				$iCalendar->clean();
				$iCalendar->addfile(\@DataArray);
			}
			# Download succesful. Set DPS_LastMD5
			$InternalConfig{DPS_LastMD5} = $MD5;
			UpdatedData();
			return(1);
		}
	} else {
		DPS_Log("Unable to download data. Server replied: $Data");
	}
	# If we got this far then it means we failed.
	return(undef);
}

# Purpose: Synchronize our local data with the server data
# Usage: DPS_DataSync();
sub DPS_DataSync {
	DPS_Status(DP_gettext('Synchronizing'),0.2);
	# Get information we need
	#	The server's data MD5 sum
	my $ServerMD5 = DPS_DataSegment('GET_MD5');
	#	The MD5 sum of our current local data
	my $LocalMD5 = md5_base64(encode_base64($iCalendar->get_rawdata(),""));
	#	The MD5 sum of the data we last uploaded
	my $LastUpMD5 = $InternalConfig{DPS_LastMD5};

	# Okay, the required information is available.
	# First check if our current local MD5 sum matches the one on the server.
	# If it does then we return without doing anything at all.
	if(defined($LocalMD5) and $ServerMD5 eq $LocalMD5) {
		DPS_Log("ServerMD5[$ServerMD5] matched our LocalMD5[$LocalMD5]. Not doing anything");
		return(1);
	} 
	# It didn't match. So now we check if our last uploaded MD5 matches the servers MD5.
	# If it does then we just upload.
	elsif ($ServerMD5 eq $LastUpMD5 or $ServerMD5 eq '[NONE]') {
		DPS_Log("Local data changed, uploading to DPS (local MD5 is $LocalMD5 and the servers MD5 is $ServerMD5)");
		DPS_Status(DP_gettext('Synchronizing'),0.4);
		my $Return = DPS_Upload();
		DPS_Status(DP_gettext('Synchronizing'),0.8);
		UpdatedData();
		DPS_Status(DP_gettext('Synchronizing'),0.9);
		return($Return);
	}
	# That didn't match either. So we check if our local MD5 is identical to the
	# last uploaded MD5. If that is the case then we just need to download the
	# data from the server.
	elsif ($LastUpMD5 eq $LocalMD5) {
		DPS_Log('Remote data changed, downloading from DPS');
		DPS_Status(DP_gettext('Synchronizing'),0.5);
		my $Return = DPS_Download();
		DPS_Status(DP_gettext('Synchronizing'),0.8);
		UpdatedData();
		DPS_Status(DP_gettext('Synchronizing'),0.9);
		return($Return);
	}
	# Okay, nothing matched. This means that we have one local MD5 sum,
	# one remote MD5 sum and one "local last uploaded" MD5 sum - and they all differ.
	# We must here download the data from the server, merge it with our own and then
	# upload the new data.
	else {
		DPS_Log('Both remote and local data has changed. Downloading, merging and reuploading');
		DPS_Status(DP_gettext('Synchronizing'),0.3);
		if(DPS_Download(1)) {
			DPS_Status(DP_gettext('Synchronizing'),0.6);
			my $Return = DPS_Upload();
			DPS_Status(DP_gettext('Synchronizing'),0.8);
			UpdatedData();
			DPS_Status(DP_gettext('Synchronizing'),0.9);
			return($Return);
		}
	}
}

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

# Purpose: High-level API to DPS
# Usage: DPS_Perform(FUNCTION);
#	FUNCTION can be one of:
#	SYNC
#	...
sub DPS_Perform {
	# The function we are going to perform
	my $Function = shift;
	# A coderef to the code which we need to run to close the GUI
	# dialogues used.
	my $GuiEnded = sub {
		return unless($Gtk2Init);
		$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{ProgressWin});
		$MainWindow->set_sensitive(1);
	};

	# Make sure that DPS is enabled in the config
	return unless(defined($UserConfig{DPS_enable}) and $UserConfig{DPS_enable} eq "1");
	# Check if the user wants to temporarily disable DPS
	return if(defined($ENV{DP_DISABLE_SERVICES}) and $ENV{DP_DISABLE_SERVICES} eq "1");
	# Verify that all required options are set in the config
	foreach my $Option (qw(host port user pass)) {
		unless(defined($UserConfig{"DPS_$Option"}) and length($UserConfig{"DPS_$Option"})) {
			DPIntWarn("DPS enabled but the setting DPS_$Option is missing. Disabling.");
			$UserConfig{DPS_enable} = 0;
			return(undef);
		} else {
			$DPServices{$Option} = $UserConfig{"DPS_$Option"};
		}
	}
	return if not DPS_SSLSocketTest();
	# Create the progress window
	if($Gtk2Init) {
		$MainWindow->set_sensitive(0);
		$DPServices{ProgressWin} = DPCreateProgressWin(DP_gettext('Services'), DP_gettext('Initializing'));
	}
	# Open up the logfile if it isn't open. This should be left open for the
	# entirety of the DPS session.
	if(not defined($DPServices{Log_FH})) {
		open($DPServices{Log_FH}, '>', "$SaveToDir/services.log");
		DPS_Log('DPS initialized');
	}
	DPS_Status(DP_gettext('Connecting'),0);
	# Connect to the server, if this fails then we return undef without doing anything
	if(not DPS_Connect()) {
		$GuiEnded->();
		return(undef);
	}
	DPS_Status(DP_gettext('Connected'),0.1);
	# Connection established, process $Function
	if($Function eq 'SYNC') {
		DPS_DataSync();
	} else {
		DPIntWarn("DPS_Perform($Function) called, but the function \"$Function\" is unknown.");
	}
	DPS_Status(DP_gettext('Complete'),1);
	# Disconnect and return
	DPS_Disconnect();
	$GuiEnded->();
	return(1);
}

# Purpose: Test for IO::Socket::SSL
# Usage: DPS_SSLSocketTest();
sub DPS_SSLSocketTest {
	# Make sure the IO::Socket::SSL module is available and loaded
	if(not $DPServices{IO_SOCKET_SSL_LOADED}) {
		if(not eval('use IO::Socket::SSL; 1')) {
			if (not $DPServices{IO_SOCKET_SSL_ERR_DISPLAYED}) {
				DPError(DP_gettext("You don't have the IO::Socket:SSL module. This module is required for the Day Planner services to function. The services will not function until this module is installed."));
			}
			$DPServices{IO_SOCKET_SSL_ERR_DISPLAYED} = 1;
			return(undef);
		} else {
			$DPServices{IO_SOCKET_SSL_LOADED} = 1;
			return(1);
		}
	}
	return(1);
}

# 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) = GetDate(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
	my $OLDLocale = setlocale(LC_ALL, 'C');		# Need errors that are not localized for IO_Socket_INET_Errors();
	$DPServices{socket} = IO::Socket::SSL->new(
					PeerAddr => $Host,
					PeerPort => $Port,
					Timeout => 10,
			) 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} == 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});

	# Authentication
	# First verify the API level
	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).");});
	# Send AUTH
	my $AUTHREPLY = DPS_DataSegment("AUTH $User $Password");
	# If AUTH did not return OK then it failed and we just return undef.
	return(undef) if DPS_ErrorIfNeeded('OK', $AUTHREPLY, sub { DPS_Disconnect(); DPS_Error(DP_gettext('The username and/or password is incorrect.'),'Authentication error');});
	DPS_Log("Connected to $Host on port $Port as user $User");
	return('OK');
}

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

# 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: 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>;
	if(not defined($Data)) {
		$Data = '';
	} else {
		chomp($Data);
	}
	return($Data);
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 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*(\w+)\s*=.*/$1/;
		$Value =~ s/^\w+=\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_LastMD5 => 'The MD5 sum of the last data downloaded from the DPS server (base64 encoded)',
		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 and defined($MainWindow)) {
			($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;
	}
	if(not defined($UserConfig{DPS_enable}) or not length($UserConfig{DPS_enable})) {
		$UserConfig{DPS_enable} = 0;
	}
	if(defined($UserConfig{DPS_pass}) and length($UserConfig{DPS_pass})) {
		$UserConfig{DPS_pass} = encode_base64(encode_base64($UserConfig{DPS_pass}));
		chomp($UserConfig{DPS_pass});
	}

	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",
		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_enable => 'If DPS (Day Planner services) is enabled or not (1/0)',
		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');
		}
	}
	# Reset DPS_pass
	if(defined($UserConfig{DPS_pass}) and length($UserConfig{DPS_pass})) {
		$UserConfig{DPS_pass} = decode_base64(decode_base64($UserConfig{DPS_pass}));
	}
}

# 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+$',
			DPS_enable => '^(1|0)$',
			DPS_port => '^\d+$',
			DPS_user => '^.+$',
			DPS_host => '^.+$',
			DPS_pass => '^.+$',
		);

	LoadConfigFile("$Dir/$File", \%UserConfig, \%OptionRegexHash,1);
	if(defined($UserConfig{DPS_pass}) and length($UserConfig{DPS_pass})) {
		$UserConfig{DPS_pass} = decode_base64(decode_base64($UserConfig{DPS_pass}));
	}
	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) {
		mkpath($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);
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 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(eval('use IPC::Open2;1')) {
				if(open2(my $SW_VERS, my $NULL_IN, 'sw_vers')) {
					while(<$SW_VERS>) {
						chomp;
						if (s/^ProductName:\s+//gi) {
							$DarwinName = $_;
						} elsif(s/^ProductVersion:\s+//) {
							$DarwinOSVer = $_;
						}
					}
					close($SW_VERS);
				}
			}
		}
		if(defined($DarwinOSVer) and defined($DarwinName)) {
			return("$DarwinName $DarwinOSVer ($machine)");
		}
	}
	return("Unknown ($sysname $release $version $machine)");
}

# Purpose: Launch a web browser with the supplied URL
# Usage: LaunchWebBrowser(URL);
sub LaunchWebBrowser {
	my $URL = shift;
	# Check if URL is a ref. If it is that means we're being used in a gtk2 callback
	# and the first arg is the object we're called from, so shift again to the second
	# arg we recieved which is the real url.
	if(ref($URL)) {
		$URL = shift;
	}
	my $Browser;
	# First check for the BROWSER env var
	if(defined($ENV{BROWSER}) and length($ENV{BROWSER})) {
		if(InPath($ENV{BROWSER}) or -x $ENV{BROWSER}) {
			$Browser = $ENV{BROWSER};
		}
	}
	# Then check for various known browsers (and browser launchers)
	if(not $Browser) {
		foreach(qw/www-browser gnome-open xdg-open mozffremote mozilla-firefox epiphany mozilla iceweasel firefox konqueror/) {
			if(InPath($_)) {
				$Browser = $_;
				last;
			}
		}
	}
	# Then launch if found, or output an error if not found
	if($Browser) {
		my $PID = fork();
		if(not $PID) {
			exec($Browser,$URL);
		}
	} else {
		DPIntWarn("Failed to detect any browser to launch for the URL $URL");
	}
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 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");
	if($LocationDetect) {
		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";
	} else {
		print $DUMMY_FILE ": This is a dummy .holiday file for Day Planner. You didn't have any\n";
		print $DUMMY_FILE ": environment variables set so Day Planner couldn't autodetect one.\n";
		print $DUMMY_FILE "\n: HOW TO REPLACE THE DUMMY FILE WITH A PROPER ONE:\n: Remove this file 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 - easy
	foreach("$ENV{HOME}/.kde/Autostart/dayplanner_auto.sh", "$ENV{HOME}/.config/autostart/dayplanner_auto.desktop",) {
		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=false\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);
			}
		}
	}
	# 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();

	# This should never be called at other times than after midnight, btu to be sure we subtract 200
	# from the time value in order to get "yesterday"
	my ($yestersec,$yestermin,$yesterhour,$yestermday,$yestermonth,$yesteryear,$yesterwday,$yesteryday,$yesterisdst) = GetDate(time - 200);
	my ($CalYear, $CalMonth, $CalDay) = $CalendarWidget->get_date();
	if($CalYear == $yesteryear && $CalMonth == $yestermonth && $CalDay == $yestermday) {
		my ($currsec,$currmin,$currhour,$currmday,$currmonth,$curryear,$currwday,$curryday,$currisdst) = GetDate(time - 200);
		$CalendarWidget->select_month($currmonth,$curryear);
		$CalendarWidget->select_day($currmday);
		CalendarChange()
	}

	# 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: Find out if a command is in PATH or not
# Usage: InPath(COMMAND);
sub InPath {
	foreach (split /:/, $ENV{PATH}) { if (-x "$_/@_" and ! -d "$_/@_" ) {   return 1; } } return 0;
}

# 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';
	return('') if not length($Time);
	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 .= HTML_Menu(@_);
	$Header .= '<hr />';
	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(not defined($NonDateMode) or not $NonDateMode eq 'M') {
		
#	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, 'Y');
	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 Planner\n";
	print $FILE HTML_PHP_DayDetectFunc();
	print $FILE '$file = DayDetectFunc("./");' . "\n";
	print $FILE 'if($file) {' . "\n";
	print $FILE "\t" . 'include($file);' . "\n";
	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(my $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") {
				while (my $File = glob("$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";
		while(my $File = glob("$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) {
				while(glob("$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

		# Append a zero to ensure proper sorting if the
		# year day is below 100
		if($TheYday < 100) {
			$TheYday = "0$TheYday";
		}
		
		$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);
		$ToolbarDeleteButton->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_Perform('SYNC');
	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);
	$AddEventBox->set_size_request(300,-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);
	
	# ==================================================================
	# 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, $ADPrimVBox, $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_url_hook(\&LaunchWebBrowser);
	$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();
	
	# Create the main VBox
	my $MainVBox = Gtk2::VBox->new();
	$MainVBox->show();
	$PreferencesWindow->add($MainVBox);
	
	# Create the notebook
	my $Notebook = Gtk2::Notebook->new();
	$Notebook->show();
	$MainVBox->pack_start($Notebook,0,0,0);
	
	# ==================================================================
	# ==================================================================
	# GENERAL TAB
	# ==================================================================
	# ==================================================================
	
	# Create the vbox
	my $Config_VBox = Gtk2::VBox->new();
	$Config_VBox->show();
	$Notebook->append_page($Config_VBox,DP_gettext('General'));
	$Config_VBox->set_border_width(12);

	# 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 label that explains about notifications
	my $NotificationLabel = Gtk2::Label->new(DP_gettext('When an event is coming up, remind me:'));
	$NotificationLabel->show();
	$NotificationLabel->set_justify('left');
	$NotificationLabel->set_alignment(0,0);
	$Events_VBox->pack_start($NotificationLabel,0,0,0);
	
	# If the user wants to be notified in advance
	my $NotifyAdvance_CheckBox = Gtk2::CheckButton->new_with_label(DP_gettext('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);
	}
	
	# 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 checkbutton
	my $EnableDisablePreNotCB = Gtk2::CheckButton->new();
	$EnableDisablePreNotCB->show();
	$TimeSel_HBox->pack_start($EnableDisablePreNotCB,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};
			});
	# TRANSLATORS: See the preferences window, this is preceeded by X minutes or X hour(s).
	my $TimeSel_Label = Gtk2::Label->new(' ' . DP_gettext('before the event time'));
	$TimeSel_Label->show();
	$TimeSel_HBox->pack_start($TimeSel_Label,0,0,0);
	
	# Set up the checkbutton signals and defaults
	$EnableDisablePreNotCB->signal_connect('toggled' => sub {
			if($EnableDisablePreNotCB->get_active()) {
				$TimeSel_Combo->set_sensitive(1);
				$TimeSel_Combo->signal_emit('changed');
			} else {
				$UserConfig{Events_NotifyPre} = 0;
				$TimeSel_Combo->set_sensitive(0);
			}});

	if($UserConfig{Events_NotifyPre}) {
		$EnableDisablePreNotCB->set_active(1);
		$EnableDisablePreNotCB->signal_emit('toggled');
	} else {
		$EnableDisablePreNotCB->set_active(0);
		$EnableDisablePreNotCB->signal_emit('toggled');
	}
	
	
	# ==================================================================
	# ==================================================================
	# SYNCHRONIZATION TAB
	# ==================================================================
	# ==================================================================
	
	# Get and set the values we're going to use
	my $Host = $UserConfig{DPS_host} ? $UserConfig{DPS_host} : '';
	my $Port = $UserConfig{DPS_port} ? $UserConfig{DPS_port} : 4435;
	my $Username = $UserConfig{DPS_user} ? $UserConfig{DPS_user} : '';
	my $Password = $UserConfig{DPS_pass} ? $UserConfig{DPS_pass} : '';
	my $DPSWasStatus = $UserConfig{DPS_enable};

	# Create the vbox
	my $Sync_VBox = Gtk2::VBox->new();
	$Sync_VBox->show();
	$Sync_VBox->set_border_width(12);
	$Notebook->append_page($Sync_VBox,DP_gettext('Synchronization'));

	# Create the table
	my $Config_Table = Gtk2::Table->new(3,4);
	$Config_Table->show();

	# Enable/disable checkbox
	my $EnableDisableCButton = Gtk2::CheckButton->new(DP_gettext('Automatically synchronize an online copy of this calendar'));
	$Sync_VBox->pack_start($EnableDisableCButton,0,0,0);
	$EnableDisableCButton->signal_connect('toggled' => sub {
			if($EnableDisableCButton->get_active) {
				if(DPS_SSLSocketTest()) {
					$UserConfig{DPS_enable} = 1;
					$Config_Table->set_sensitive(1);
				}
			} else {
				$UserConfig{DPS_enable} = 0;
				$Config_Table->set_sensitive(0);
			}});
	if($UserConfig{DPS_enable}) {
		$EnableDisableCButton->set_active(1);
		$EnableDisableCButton->signal_emit('toggled');
	} else {
		$EnableDisableCButton->set_active(0);
		$EnableDisableCButton->signal_emit('toggled');
	}
	$EnableDisableCButton->show();

	# Add the table to the UI
	$Sync_VBox->pack_start($Config_Table,0,0,0);
	
	# ==================================================================
	# HOST/PORT
	# ==================================================================
	
	# Host

	#  Label
	my $UseHost = Gtk2::Label->new(DP_gettext('Server:'));
	$UseHost->show();
	$Config_Table->attach_defaults($UseHost, 0,1,0,1);
	
	#  Entry
	my $HostEntry = Gtk2::Entry->new();
	$HostEntry->set_text($Host);
	$HostEntry->show();
	$Config_Table->attach_defaults($HostEntry, 1,2,0,1);
	
	# Port
	
	#  Label
	my $UsePort = Gtk2::Label->new(' ' . DP_gettext('Port:'));
	$UsePort->show();
	$Config_Table->attach_defaults($UsePort, 2,3,0,1);
	
	#  Spinner
	my $PortAdjustment = Gtk2::Adjustment->new(0.0, 0.0, 65000.0, 1.0, 5.0, 0.0);
	my $PortSpinner = Gtk2::SpinButton->new($PortAdjustment,0,0);
	$PortSpinner->set_value($Port);
	$PortSpinner->show();
	$Config_Table->attach_defaults($PortSpinner, 3,4,0,1);
	
	# ==================================================================
	# USERNAME/PASSWORD
	# ==================================================================
	
	# Username
	#  Label
	my $UsernameLabel = Gtk2::Label->new(DP_gettext('Username:'));
	$UsernameLabel->show();
	$Config_Table->attach_defaults($UsernameLabel, 0,1,1,2);
	
	#  Entry
	my $UserEntry = Gtk2::Entry->new();
	$UserEntry->set_text($Username);
	$UserEntry->show();
	$Config_Table->attach_defaults($UserEntry, 1,4,1,2);
	
	# Password
	#  Label
	my $PasswordLabel = Gtk2::Label->new(DP_gettext('Password:'));
	$PasswordLabel->show();
	$Config_Table->attach_defaults($PasswordLabel, 0,1,2,3);
	
	#  Entry
	my $PasswordEntry = Gtk2::Entry->new();
	$PasswordEntry->set_text($Password);
	$PasswordEntry->show();
	$PasswordEntry->set_visibility(0);
	$Config_Table->attach_defaults($PasswordEntry, 1,4,2,3);
	
	# ==================================================================
	# FINALIZE WINDOW
	# ==================================================================
	my $ClosePerform = sub {
			if(not defined($UserConfig{DPS_user}) or (not($UserConfig{DPS_user} eq $UserEntry->get_text()))) {
				$UserConfig{DPS_user} = $UserEntry->get_text();
				$DPSWasStatus = 0;
			}
			if(not defined($UserConfig{DPS_pass}) or (not($UserConfig{DPS_pass} eq $PasswordEntry->get_text()))) {
				$UserConfig{DPS_pass} = $PasswordEntry->get_text();
				$DPSWasStatus = 0;
			}
			if(not defined($UserConfig{DPS_host}) or (not($UserConfig{DPS_host} eq $HostEntry->get_text()))) {
				$UserConfig{DPS_host} = $HostEntry->get_text();
				$DPSWasStatus = 0;
			}
			if(not defined($UserConfig{DPS_port}) or not($UserConfig{DPS_port} eq $PortSpinner->get_value())) {
				# Don't write the port if it is currently empty and DPS isn't enabled
				if (defined($UserConfig{DPS_port}) or ($UserConfig{DPS_enable})) {
					$UserConfig{DPS_port} = $PortSpinner->get_value();
					$DPSWasStatus = 0;
				}
			}
			# Make sure that the proper DPS settings are in place
			if($UserConfig{DPS_enable}) {
				if(not $UserConfig{DPS_user} or not $UserConfig{DPS_pass} or not $UserConfig{DPS_host} or not $UserConfig{DPS_port}) {
					DPError(DP_gettext('You have not entered the information required for synchronization to be enabled, it has been disabled.'));
					$UserConfig{DPS_enable} = 0;
				}
			}
			WriteConfig();
			$PreferencesWindow->hide();
			# Synchronize if DPS settings have been changed and it is enabled
			if($UserConfig{DPS_enable} and not $DPSWasStatus) {
				DPS_Perform('SYNC');
			}
			$PreferencesWindow->destroy();
			$MainWindow->set_sensitive(1);
		};
	# Handle closing
	$PreferencesWindow->signal_connect('delete-event' => $ClosePerform);
	
	# Add the buttons
	my $ButtonHBox = Gtk2::HBox->new();
	$ButtonHBox->show();
	$MainVBox->pack_start($ButtonHBox,0,0,0);

	my $CloseButton = Gtk2::Button->new_from_stock('gtk-close');
	$CloseButton->signal_connect('clicked' => $ClosePerform);
	$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);
	$ToolbarDeleteButton->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 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);
			$ToolbarDeleteButton->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);

	# ==================================================================
	# 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);
	
	# ==================================================================
	# LEFT HAND AREA
	# ==================================================================
	
	# Create the vbox for use in it
	my $LeftHandVBox = Gtk2::VBox->new();
	$WorkingAreaHBox->pack_start($LeftHandVBox,1,1,0);
	$LeftHandVBox->show();
	
	# Add a window for use for it
	$EventlistWin = Gtk2::ScrolledWindow->new;
	$EventlistWin->set_policy('automatic', 'automatic');
	$LeftHandVBox->pack_start($EventlistWin,1,1,0);
	$EventlistWin->show();

	# ==================================================================
	# TOOLBAR
	# ==================================================================
	
	# Tooltips
	my $Tooltips = Gtk2::Tooltips->new();
	$Tooltips->enable();
	
	# Toolbar
	$Toolbar = Gtk2::Toolbar->new();
	$Toolbar->set_style('icons');
	$LeftHandVBox->pack_end($Toolbar,0,0,0);
	$Toolbar->show();
	
	# Delete button
	$ToolbarDeleteButton = Gtk2::ToolButton->new_from_stock('gtk-delete');
	$ToolbarDeleteButton->set_tooltip($Tooltips,DP_gettext('Delete the selected event'),'');
	$Tooltips->set_tip($ToolbarDeleteButton,DP_gettext('Delete the selected event'));
	$ToolbarDeleteButton->signal_connect('clicked' => \&DeleteEvent);
	$Toolbar->insert($ToolbarDeleteButton,0);
	$ToolbarDeleteButton->show();

	# Edit button
	$ToolbarEditButton = Gtk2::ToolButton->new_from_stock('gtk-edit');
	$ToolbarEditButton->set_tooltip($Tooltips,DP_gettext('Edit the selected event'),'');
	$Tooltips->set_tip($ToolbarEditButton,DP_gettext('Edit the selected event'));
	$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();
	$AddButton->set_tooltip($Tooltips,DP_gettext('Add a new event'),'');
	$Tooltips->set_tip($AddButton,DP_gettext('Add a new event'));

	$Toolbar->get_nth_item(0)->set_is_important(1);
	$Toolbar->get_nth_item(1)->set_is_important(1);
	$Toolbar->set_tooltips(1);
	
	# Draw the initial eventlist
	DrawEventlist();
}

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

# Purpose: Call the main data initialization functions
# Usage: MainInit();
sub MainInit {
	my $NoGui = $_[0];
	my $FirstStartup;
	# Set SaveToDir if it isn't already set
	if(not defined($SaveToDir)) {
		$SaveToDir = DetectConfDir();
	}
	# Set HolidayFile
	$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: Initialize the core 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_Perform('SYNC');
	# 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();
}

# Purpose: Detect the user config  directory
# Usage: DetectConfDir();
sub DetectConfDir {
	# First detect the HOME directory, and set $ENV{HOME} if successfull,
	# if not we just fall back to the value of $ENV{HOME}.
	my $HOME = getpwuid($>);
	if(-d $HOME) {
		$ENV{HOME} = $HOME;
	}
	# Compatibility mode, using the old conf dir
	if(-d "$ENV{HOME}/.dayplanner") {
		return("$ENV{HOME}/.dayplanner");
	}
	# Check for XDG_CONFIG_HOME in the env
	my $XDG_CONFIG_HOME;
	if(defined($ENV{XDG_CONFIG_HOME})) {
		$XDG_CONFIG_HOME = $ENV{XDG_CONFIG_HOME};
	} else {
		if(defined($ENV{HOME}) and length($ENV{HOME})) {
			# Verify that HOME is set properly
			if(not -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");
			}
			$XDG_CONFIG_HOME = "$ENV{HOME}/.config";
		} else {
			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"));
		}
	}
	return("$XDG_CONFIG_HOME/dayplanner");
}

# 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 {
		$SaveToDir = DetectConfDir();
		print "Day Planner version $Version\n";
		print "RCS revision: $RCSRev\n";
		print "Using config dir: $SaveToDir\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 IO::Socket::SSL IPC::Open2/) {
			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] : '';
		$SaveToDir = DetectConfDir();
		$SaveToDir .= "/debug$NumberPrefix";
		my $Dir = $SaveToDir;
		$Dir =~ s/^$ENV{HOME}/~/g;
		DPIntInfo("Running in test mode (using $Dir)");
	},
	'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 {
		CLI_Export(@_);
	},
	'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;

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