#!/usr/bin/perl
# Day Planner
# A graphical Day Planner written in perl that uses Gtk2
# Copyright (C) Eskild Hustvedt 2006, 2007, 2008
# $Id: dayplanner 2300 2008-08-04 15:27:01Z 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 3 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, see <http://www.gnu.org/licenses/>.

# vim: set tabstop=4 shiftwidth=4 :

require 5.8.0;			# We need at least perl 5.8.0
use strict;				# Force strict coding
use warnings;			# Tell perl to warn about things
use POSIX;				# Uses the various time/date functions from POSIX
use Gtk2;				# Use Gtk2 :)
use Gtk2::SimpleList;	# We use Gtk2::SimpleList to create the eventlist
use Gtk2::Gdk::Keysyms;	# Easier keybindings
use MIME::Base64;		# Used for DPS-related functions. Needed even when DPS is diasbled. 
use Getopt::Long;		# Commandline options
use FindBin;			# So that we can detect module dirs during runtime
# Useful constants for prettier code
use constant { true => 1, false => 0 };
# This here is done so that we can use local versions of our libs
use lib "$FindBin::RealBin/modules/Date-HolidayParser/lib/";
use lib "$FindBin::RealBin/modules/DP-iCalendar/lib/";
use lib "$FindBin::RealBin/modules/DP-GeneralHelpers/lib/";
use lib "$FindBin::RealBin/modules/DP-CoreModules/lib/";
use lib "$FindBin::RealBin/modules/";
# Day Planner-specific libs
use Date::HolidayParser 0.4;	# Parsing of .holiday files
use DP::CoreModules;
use DP::iCalendar qw(iCal_ParseDateTime iCal_GenDateTime); # iCalendar support
use DP::iCalendar::Manager;		# Support for multiple iCalendars using a single object
use DP::GeneralHelpers qw(DPIntWarn DPIntInfo WriteConfigFile LoadConfigFile AppendZero);
use DP::GeneralHelpers::IPC;
use DP::GeneralHelpers::I18N;

# Scalars
our $Version = '0.9.2';
my $VersionName = 'Late Summer';
my $RCSRev = '$Id: dayplanner 2300 2008-08-04 15:27:01Z zero_dogg $';
my $SaveToDir;						# The configuration and eventlist directory (set later)
my $HolidayFile;					# The file to load holiday definitions from (set later)
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 $DaemonInitialized = 0;			# Has the daemon been initialized?
my $DaemonSocketName = 'Daemon_Socket';# The name of the daemon socket
my $DaemonSocket;					# The variable to connect to
my $i18n;
my $DPS_APILevel = '06';			# The DPS API level used/supported
my $iCalendar;						# The DP::iCalendar::Manager object.
my $IPC_Socket;
my $HTML_PHP;

# 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

# 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

# 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: Initialize the i18n subsystem
# Usage: DP_InitI18n();
sub DP_InitI18n {
	if ($i18n)
	{
		DPIntWarn("Second call to DP_InitI18n(). This is bad.");
	}
	else
	{
		$i18n = DP::GeneralHelpers::I18N->new();
	}
}

# Purpose: i18n wrapper for CoreModules
# Usage: i18nwrapper(SAME AS $I18n->get);
sub i18nwrapper
{
	return($i18n->get(@_));
}

# Purpose: i18n wrapper for CoreModules
# Usage: i18nwrapper_advanced(SAME AS $I18n->get_advanced);
sub i18nwrapper_advanced
{
	return($i18n->get_advanced(@_));
}

# Purpose: i18n wrapper for CoreModules
# Usage: i18nwrapper_AMPM_From24(SAME AS $I18n->AMPM_From24);
sub i18nwrapper_AMPM_From24
{
	return($i18n->AMPM_From24(@_));
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Daemon communication functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Purpose: Initialize and connect to the daemon
# Usage: DaemonInit();
sub DaemonInit {
	$DaemonInitialized = 1;
	DaemonConnect();
	if (not $DaemonSocket) {
		if(not StartDaemon()) {
			return(undef);
		}
		if(not DaemonConnect())
		{
			DPIntWarn("DaemonInit(): Failed, even after attempted daemon startup. Will attempt to restart DaemonInit()");
			return(DaemonInit());
		}
	}
	return(true);
}

# Purpose: Connect to the daemon, returning the IPC object
# Usage: DaemonConnect();
sub DaemonConnect
{
	if (-e "$SaveToDir/$DaemonSocketName") {
		my $daemonConnErr;
		($DaemonSocket,$daemonConnErr) = DP::GeneralHelpers::IPC->new_client("$SaveToDir/$DaemonSocketName",sub { return });
		return($DaemonSocket);
		# TODO: Implement a IPC->blocking_client_send() and use that to check daemon VERSION
	}
	return(false);
}

# Purpose: Start the Day Planner daemon
# Usage: StartDaemon();
sub StartDaemon {
	foreach(split(/:/, sprintf('%s:%s', $FindBin::RealBin, $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)) {
				return(true);
			}
			else
			{
				DPIntWarn("StartDaemon(): Tried to start '$_/$DaemonName' but it failed, will attempt to locate another daemon");
			}
		}
	}
	DPIntWarn('Unable to start daemon!');
	return(0);
}

# Purpose: Close the connection to the daemon (shutdown if $ShutdownDaemon) and close the IPC socket
# Usage: CloseIPC();
sub CloseIPC {
	if($ShutdownDaemon) {
		$DaemonSocket->client_send($$.' SHUTDOWN');
	}
	if($IPC_Socket) {
		$IPC_Socket->destroy();
	}
	if($DaemonSocket) {
		$DaemonSocket->destroy();
	}
	return(1);
}

# Purpose: Send a message to the daemon
# Usage: Daemon_SendData(DATA);
sub Daemon_SendData {
	if(not $DaemonSocket) {
		return(undef);
	}
	$DaemonSocket->client_send("$$ $_[0]");
}

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

# 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($i18n->get('An error ocurred while uploading the data'), 'An error ocurred during upload of the data: ' . $Reply);
		} elsif($Reply =~ /^EXPIRED/) {
			DPS_Error($i18n->get('Your account has expired. If you are using a paid service this may be because you have not paid 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($i18n->get_advanced('The server did not accept the uploaded data and replied with an unknown value: %(value)', { value => $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($i18n->get('The data became corrupted 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
			my $iCalendarMain = $iCalendar->get_primary();
			if($Merge)
			{
				$iCalendarMain->enable('SMART_MERGE');
				$iCalendarMain->addfile(\@DataArray);
				$iCalendarMain->disable('SMART_MERGE');
			} 
			else 
			{
				$iCalendarMain->clean();
				$iCalendarMain->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($i18n->get('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 defined($ServerMD5) 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($i18n->get('Synchronizing'),0.4);
		my $Return = DPS_Upload();
		DPS_Status($i18n->get('Synchronizing'),0.8);
		UpdatedData();
		DPS_Status($i18n->get('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($i18n->get('Synchronizing'),0.5);
		my $Return = DPS_Download();
		DPS_Status($i18n->get('Synchronizing'),0.8);
		UpdatedData();
		DPS_Status($i18n->get('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($i18n->get('Synchronizing'),0.3);
		if(DPS_Download(1)) {
			DPS_Status($i18n->get('Synchronizing'),0.6);
			my $Return = DPS_Upload();
			DPS_Status($i18n->get('Synchronizing'),0.8);
			UpdatedData();
			DPS_Status($i18n->get('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 {
	runtime_use('Digest::MD5 qw(md5_base64 md5_hex)');
	# 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);
		DP_DestroyProgressWin($DPServices{ProgressWin});
		if(defined($DPServices{Error})) {
			DPError($i18n->get_advanced("An error occurred with the Day Planner services:\n\n%(error)",{ error => $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($i18n->get('Services'), $i18n->get('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");
		chmod(oct(600),"$SaveToDir/services.log");
		DPS_Log('DPS initialized');
	}
	DPS_Status($i18n->get('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($i18n->get('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($i18n->get('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 runtime_use('IO::Socket::SSL',true)) {
		if (not $DPServices{IO_SOCKET_SSL_ERR_DISPLAYED}) {
			DPError($i18n->get("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} = true;
		}
		return(false);
	}
	return(true);
}

# Purpose: Connect to a Day Planner services server.
# Usage: my $Return = DPS_Connect();
#		The arguments are optional and will be read from %DPServices if not supplied
sub DPS_Connect {
	my $Host = $DPServices{host};
	my $Port = $DPServices{port};
	my $User = $DPServices{user};
	my $Password = $DPServices{pass};
	my $Error;
	# Connect
	$DPServices{socket} = IO::Socket::SSL->new(
					PeerAddr => $Host,
					PeerPort => $Port,
					Timeout => 10,
			) or do { $Error = IO::Socket::SSL::errstr(); };
			
	# 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($i18n->get('Unable to connect to the Day Planner services server (%s).'), "$Host:$Port",) . " " . $i18n->get("You're probably not connected to the internet"), "Unable to connect to $Host on port $Port: $@ ($Error)");
		}
			# Process connection refused
		elsif($Error eq 'REFUSED') {
			$DPServices{Offline} = 1;
			DPS_Error(sprintf($i18n->get('Unable to connect to the Day Planner services server (%s).'), "$Host:$Port") . ' ' . $i18n->get('The connection was refused by the server. Please verify your Day Planner services settings.') . "\n\n" . $i18n->get('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($i18n->get("Unable to connect to the Day Planner services server (%s)."), "$Host:$Port") . " " . $i18n->get('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($i18n->get_advanced("The Day Planner services server you are connecting to does not support this version of Day Planner (%(version)).", { version =>  $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($i18n->get('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 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',
		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 {
	P_WriteConfig($SaveToDir,$ConfigFile,%UserConfig);
}

# Purpose: Load the configuration file
# Usage: LoadConfig();
sub LoadConfig {
	%UserConfig = P_LoadConfig($SaveToDir,$ConfigFile);
	return(1);
}

# Purpose: Create the directory in $SaveToDir if it doesn't exist and display a error if it fails
# Usage: CreateSaveDir();
sub CreateSaveDir {
	return P_CreateSaveDir($SaveToDir);
}

# 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 {
	$iCalendar->write();	# TODO: Check return value
	# This to avoid unneccesary overhead in the daemon reloading files needlessly
	Daemon_SendData('RELOAD_DATA');
	return(true);
}

# Purpose: Save the data file and redraw the needed windows
# Usage: UpdatedData(DONT_SAVE?, DONT_REDRAW_EVENTLIST);
sub UpdatedData {
	my ($DontSave,$DontRedrawEventList) = @_;
	if($MainWindow) {
		if(not $DontRedrawEventList) {
			# Redraw the event list
			DrawEventlist();
		}
		# Repopulate the upcoming events
		PopulateUpcomingEvents();
		# Redraw the calendar
		CalendarChange();
	}
	# Make buttons active/inactive
	EventListToggle();
	# 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 HTTP subscriptions
# Usage: LoadHTTPSubscriptions;
sub LoadHTTPSubscriptions
{
	if(not defined($UserConfig{HTTP_Calendars}) or not length($UserConfig{HTTP_Calendars}) > 7)
	{
		return;
	}
	my $added = false;
	runtime_use('DP::iCalendar::HTTPSubscription') or return();
	# Usage: my $ProgressWin = DPCreateProgressWin(WINDOW NAME, INITIAL PROGRESS BAR TEXT, PULSATE_MODE?);
	my $ProgressWin = DPCreateProgressWin($i18n->get('Calendar subscriptions'),$i18n->get('Downloading'),true);
	foreach my $subscription(split(/\s+/,$UserConfig{HTTP_Calendars}))
	{
		PulsateProgressbar($ProgressWin);
		if(not $subscription =~ /^(http|webcal)/)
		{
			my $scheme = $subscription;
			$scheme =~ s/^(.+):.*$/$1/;
			DPIntWarn("Unknown URI-scheme: $scheme (in $subscription): should be either http:// or webcal://");
			next;
		}
		my $lastupdate = 0;
		my $updates = 0;
		my $loopturn = 1;
		my $maxturns = 8;
		my $obj = DP::iCalendar::HTTPSubscription->new($subscription,
			sub {
				my $lt = time();
				if ($lastupdate != $lt)
				{
						$updates = 0;
						$loopturn = 1;
				}
				if($updates != 2)
				{
					if ($loopturn == 1 or $loopturn == $maxturns)
					{
						$updates++;
						$lastupdate = $lt;
						PulsateProgressbar($ProgressWin);
					}
				}
				if ($loopturn == $maxturns)
				{
					$loopturn = 0;
				}
				$loopturn++;
			}, $SaveToDir
		);
		$iCalendar->add_object($obj,false);
		$added = true;
	}
	if ($added)
	{
		PulsateProgressbar($ProgressWin);
		UpdatedData(true,false);
		PulsateProgressbar($ProgressWin);
	}
	DP_DestroyProgressWin($ProgressWin);
}

# Purpose: Load the calendar contents
# Usage: LoadCalendar();
sub LoadCalendar {
	my $IsFirstStartup = shift;
	# Create the manager
	$iCalendar = DP::iCalendar::Manager->new();
	my $MainCalendar;
	if(-e "$SaveToDir/$ICSFile") {
		# Load an already existing calendar
		$MainCalendar = DP::iCalendar->new("$SaveToDir/$ICSFile");
	} else {
		# Create a new calendar
		$MainCalendar = DP::iCalendar->newfile("$SaveToDir/$ICSFile");
		# Save it
		$MainCalendar->write();
	}
	# Relax the file permissions if we're told to
	if (defined($ENV{DP_NO_STRICT_PERMS}) and $ENV{DP_NO_STRICT_PERMS} eq '1')
	{
		$MainCalendar->set_file_perms(oct(644));
	}
	# Manage it
	$iCalendar->add_object($MainCalendar,true);
	# Make sure we have a holidays file before trying to load one
	if (not -e "$SaveToDir/holidays") 
	{
		HolidaySetup();
	}
	# If we still don't have one then skip this step
	if (-e "$SaveToDir/holidays")
	{
		# Load the holiday file
		$HolidayParser = Date::HolidayParser->new("$SaveToDir/holidays");
		# Enable Date::HolidayParsers DP::iCalendar-compatible interface, replacing
		# the standard interface.
		$HolidayParser->enable_ical_interface();
		# Add the holiday parser to the manager
		$iCalendar->add_object($HolidayParser,false);
	}
	# Set the ICS prodid
	$iCalendar->set_prodid("-//day-planner.org//NONSGML Day Planner $Version//EN");
	# Apply bypassing if requested
	if (defined($ENV{DP_BYPASS_MANAGER}) and $ENV{DP_BYPASS_MANAGER} eq '1')
	{
		DPIntWarn("Bypassing DP::iCalendar::Manager - this is unsupported and should only be used for debugging.");
		$iCalendar = $MainCalendar;
	}
	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];
}

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

# Purpose: Display an error about an already running Day Planner session
# Usage: AlreadyRunningDP(IPC_CLIENT);
sub AlreadyRunningDP {
	my $ClientConnection = shift;
	if($Gtk2Init) {
		my $ErrorDialog = Gtk2::MessageDialog->new (undef,
						'destroy-with-parent',
						'error',
						'none',
						 $i18n->get("Another instance of Day Planner appears to be running. Please quit the currently running instance of Day Planner before continuing\n\nYou may force the other instance to quit if you wish."));
		$ErrorDialog->set_title($i18n->get('Day Planner'));

		$ErrorDialog->add_buttons(
			# TRANSLATORS: As in force another running Day Planner instance to quit
			$i18n->get('_Force') => 'reject',
			'gtk-ok' => 'accept',
		);
	
		$ErrorDialog->set_default_response('accept');
		if($ErrorDialog->run eq 'accept') {
			$ErrorDialog->destroy();
			$ClientConnection->destroy();
			exit(0)
		} else {
			$ErrorDialog->destroy;
			$ClientConnection->client_send('QUIT');
			$ClientConnection->destroy();
			# Sleep for one second to allow the other instance to quit
			sleep(1);
			# Unlink the IPC socket
			unlink("$SaveToDir/ipcsocket");
			$iCalendar->reload();
			return(true);
		}
	} else {
		$ClientConnection->destroy();
		die("Another instance of Day Planner appears to be running, unable to continue\n");
	}
}

# Purpose: Handle IPC talking
# Usage: IPC_Handler();
sub IPC_Handler {
	my $Message = shift;
	chomp($Message);
	if($Message =~ s/^IMPORT_DATA\s+(.+)$/$1/) {
		$iCalendar->addfile($Message);
		print "Imported $Message\n";
		UpdatedData();
	} elsif ($Message =~ /^QUIT/) {
		# This is the same as ctrl+c
		DP_SigHandler('IPC_QUIT_REQUEST');
	} else {
		return('UNKNOWN_CMD');
	}
}

# Purpose: Upgrade from one version to another
# Usage: UpgradeVersion();
sub UpgradeVersion {
	my $ProgressWin = DPCreateProgressWin($i18n->get('Upgrading'), '', 1);
	$InternalConfig{LastVersion} = $Version;
	my $Total = 9;
	# Upgrade the holiday file if needed
	if(-e $HolidayFile) {
		ProgressMade(1, $Total, $ProgressWin) if($Gtk2Init);
		if(-e "$FindBin::RealBin/holiday/dayplanner_upgrade") {
			runtime_use('Digest::MD5 qw(md5_base64 md5_hex)');
			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}") {
					runtime_use('File::Copy');
					# 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/birthdays.dpd");
			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} = $i18n->get_advanced("%(name)'s birthday",{ name => $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);
	# Upgrade old daemon (ie. kill it) if needed
	if (-e "$SaveToDir/dayplannerd")
	{
		my $Old_Daemon = DP::GeneralHelpers::IPC->new_client("$SaveToDir/dayplannerd");
		# Authenticate as a commander, this has no access control restrictions.
		$Old_Daemon->client_send("$$ HI commander");
		$Old_Daemon->client_send("$$ SHUTDOWN");
		$Old_Daemon->destroy();
		# Ensure that the socket is gone
		unlink("$SaveToDir/dayplannerd") if -e "$SaveToDir/dayplannerd";
	}
	ProgressMade(8, $Total, $ProgressWin) if($Gtk2Init);
	# Remove and re-add autostart if it is enabled, so that
	# the autostart is correct (daemon path might have changed)
	if ($InternalConfig{AutostartOn} eq '1')
	{
		RemoveAutostart();
		DP_AddAutostart();
	}
	ProgressMade(9, $Total, $ProgressWin) if($Gtk2Init);
	DP_DestroyProgressWin($ProgressWin);
}

# Purpose: Handle various signals gracefully
# Usage: $SIG{SIGNAL} = \&DP_SigHandler;
sub DP_SigHandler {
	$| = 1;
	print "SIG$_[0] recieved.\nSaving data...";
	if($iCalendar) {
		if(SaveMainData() eq 'SAVE_FAILED') {
			print 'FAILED';
		} else {
			print 'done';
		}
	} else {
		print 'not needed';
	}
	print "\nWriting state file...";
	if($SaveToDir and scalar keys %InternalConfig) {
		WriteStateFile($SaveToDir, "state.conf");
		print "done\n";
	} else {
		print "not needed\n";
	}
	print "Closing the daemon connection...";
	CloseIPC();
	print "done\nExiting the gtk2 main loop...";
	if($Gtk2Init) {
		Gtk2->main_quit;
		print "done\nExiting\n";
	} else {
		print "not running\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;

	# First try to fetch from the environment
	foreach my $var(qw(LC_ADDRESS LC_TELEPHONE LC_IDENTIFICATION LC_MESSAGES LC_ALL LANGUAGE LANG LC_MONETARY LC_NUMERIC LC_TIME LC_COLLATE LC_CTYPE))
	{
		if(defined($ENV{$var}))
		{
			$LocationDetect = $ENV{$var};
			last;
		}
	}
	# If that didn't work, try to get it from setlocale()
	if(not $LocationDetect)
	{
		foreach my $var([LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME])
		{
			$LocationDetect = setlocale($var);
			# If it is defined, not of zero length and not set to C then we can use it, if not
			# then discard this value and continue
			if(defined($LocationDetect) and length($LocationDetect) and not $LocationDetect eq 'C')
			{
				last;
			}
			else
			{
				$LocationDetect = undef;
			}
		}
	}
	# If THAT didn't work either, fall back to guessing environment variables
	if(not $LocationDetect)
	{
		my $BaseWarning = 'Neither of the environment variables LC_ADDRESS, LC_TELEPHONE, LC_IDENTIFICATION, LC_MESSAGES, LC_ALL nor LANGUAGE was set. ';
		# Fall back to automatic detection
		foreach my $key (keys(%ENV))
		{
			if ($key =~ /^(LANG|LC_)/)
			{
				my $cont = $ENV{$key};
				if ($cont =~ /^\w\w_\w\w(\..*)/)
				{
					DPIntWarn($BaseWarning . "Unable to reliably detect .holiday file. Fell back to alternate method, found $key in environment ($key=$cont) - using that for detection");
				}
				else
				{
					DPIntWarn($BaseWarning.'Unable to detect .holiday file');
				}
			}
		}
	}

	my $CopyFile;
	
	if(defined($LocationDetect)) {
		# Let's try to find the LocationDetect value 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") {
				runtime_use('File::Copy');
				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. Remove the entry Holiday_Attempted\n: in state.conf and re-run Day Planner.\n";
		print $DUMMY_FILE ": Make sure one of the LC_* or LANG* environment variables are set when running Day Planner";
	}
	print $DUMMY_FILE "\n\n: Day Planner will automatically re-try to detect a holiday file after it has been updated.";
	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', $FindBin::RealBin, $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") or warn("Unable to open $ENV{HOME}/.kde/Autostart/dayplanner_auto.sh for writing: $!");
		if($KDESCRIPT) {
			print $KDESCRIPT "#!/bin/sh\n# Autogenerated startup script written by Day Planner\n";
			print $KDESCRIPT "$DaemonExec\n";
			close($KDESCRIPT);
			chmod(oct(700), $ENV{HOME}.'/.kde/Autostart/dayplanner_auto.sh');
		}
	}
	# 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 "Version=1.0\n";
		print $XDGSTART "Encoding=UTF-8\n";
		print $XDGSTART "Type=Application\n";
		print $XDGSTART "Name=Day Planner Reminder\n";
		print $XDGSTART "Comment=Ensures you get notified about events\n";
		print $XDGSTART "Exec=$DaemonExec\n";
		print $XDGSTART "StartupNotify=false\n";
		print $XDGSTART "StartupWMClass=false\n";
		print $XDGSTART "Terminal=false\n";
		# To ensure it is autostarted, we add this. It isn't strictly required, but doesn't hurt
		print $XDGSTART "X-GNOME-Autostart-enabled=true\n";
		# This is hacky, but we'll try anyway
		my $Locale = setlocale(LC_ALL);
		$Locale =~ s/^(\w\w)(_\w\w)?.*/$1$2/;
		if(not $Locale =~ /^en/ and length($Locale))
		{
			my $LocalizedName = $i18n->get('Day Planner Reminder');
			$LocalizedName = undef
				if ($LocalizedName eq 'Day Planner Reminder');
			# TRANSLATORS: This is the comment used in the autostart .desktop file about the Day Planner Reminder
			my $LocalizedComment = $i18n->get('Ensures you get notified about events');
			$LocalizedComment = undef
				if ($LocalizedComment eq 'Ensures you get notified about events');
			# We print twice to ensure the locale is correct
			print $XDGSTART "Name[$Locale]=$LocalizedName\n" if $LocalizedName;
			print $XDGSTART "Comment[$Locale]=$LocalizedComment\n" if $LocalizedComment;
			$Locale =~ s/^(\w\w).*/$1/;
			print $XDGSTART "Name[$Locale]=$LocalizedName\n" if $LocalizedName;
			print $XDGSTART "Comment[$Locale]=$LocalizedComment\n" if $LocalizedComment;
		}
		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', $FindBin::RealBin, $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 eq 'okay';

	if($Result eq '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
			# TRANSLATORS: This string appears when Day Planner fails to add dayplanner-daemon
			# 	to autostart in old GNOME releases (version 2.12 and older).
			# 	I am aware of the fact that it may not apply everywhere, but it's
			# 	about as close as it gets, and users running GNOME 2.13 and later or
			# 	not running GNOME at all will not see this.
			DPError($i18n->get("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($i18n->get('Disabling automatic startup of the reminder will prevent notifications unless Day Planner has been manually started. Disable 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, but 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);
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Evolution compat mode
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Evolution compat mode is a very hacky temporary solution to the issue of
# GNOME panel integration. It spits out symlinks that makes e-d-s read the
# day planner iCalendar file instead of the real evolution file, and adds
# a symlink in PATH that does evolution -> dayplanner so that when an event
# from the panel is edited, day planner will pop up instead of evolution.
#
# This has the unfortunate side-effect of rendering evolution partly unusable
# for the user in question, because day planner will often be launched instead
# of evolution.
#
# TODO:
# - Add evolution wrapping functions into day planner that takes action before
# 	day planner option parsing occurs, this will enable us to only ever intercept
# 	--component=calendar calls, passing the rest on to the real evolution.

# Purpose: Get evolution compat mode. Returns true if enabled.
# Usage: EvoCompat_GetMode();
sub EvoCompat_GetMode
{
	if(not -d $ENV{HOME}.'/.evolution')
	{
		return(false);
	}
	if(-l $ENV{HOME}.'/.evolution/calendar/local/system/calendar.ics')
	{
		if (not -l $ENV{HOME}.'/.local/bin/evolution' or not readlink($ENV{HOME}.'/.local/bin/evolution') =~ /dayplanner$/)
		{
			return(true);
		}
		elsif (not readlink($ENV{HOME}.'/.evolution/calendar/local/system/calendar.ics') eq $SaveToDir.'/'.$ICSFile)
		{
			my $link = readlink($ENV{HOME}.'/.evolution/calendar/local/system/calendar.ics');
			$link =~ s/^.*(dayplanner.*)$/$1/;
			if ($link =~ /dayplanner/)
			{
				DPIntWarn("EvoCompat_GetMode(): another day planner instance appears to be in evolution compat mode: $link");
			}
			return (false);
		}
		else
		{
			return(false);
		}
	}
	else
	{
		return(false);
	}
}

# Purpose: Enable evolution compat mode
# Usage: EvoCompat_Enable();
sub EvoCompat_Enable
{
	my $Total = 5;
	if(not EvoCompat_GetMode())
	{
		# Check for e-d-s
		open(my $PS, 'ps uxw|');
		my $oldslash = $/;
		undef $/;
		my $PSIn = <$PS>;
		$/ = $oldslash;
		close($PS);
		my $EDSExists;
		foreach (split(/\n/,$PSIn))
		{
			next if not /evolution-data-server/;
			$EDSExists = true;
		}
		if(not $EDSExists)
		{
			DPIntWarn("Unable to enable evolution compat: evolution-data-server appears to not be running");
			return false;
		}

		my $ProgressWin = DPCreateProgressWin("Enabling evolution compatibility");
		my $EVO_Target_Link = $ENV{HOME}.'/.evolution/calendar/local/system/calendar.ics';
		my $DP_EVO_Link = $ENV{HOME}.'/.local/bin/evolution';
		ProgressMade(1, $Total, $ProgressWin);
		if (-e $EVO_Target_Link)
		{
			if (-l $EVO_Target_Link)
			{
				unlink($EVO_Target_Link);
			}
			elsif (not -e $EVO_Target_Link.'.dayplanner_evocompat_backup')
			{
				runtime_use('File::Copy qw(move)');
				move($EVO_Target_Link,$EVO_Target_Link.'.dayplanner_evocompat_backup') or DPIntWarn("Unable to move Evolutions calendar.ics to backup file: $!");
			}
			else
			{
				DPIntWarn("Refusing to enable Evolution compat: an earlier day planner evocompat file was found.");
				DP_DestroyProgressWin($ProgressWin);
				return false;
			}
		}
		ProgressMade(2, $Total, $ProgressWin);
		symlink($SaveToDir.'/'.$ICSFile,$EVO_Target_Link) or DPIntWarn("Unable to symlink Day Planner's calendar.ics over Evolution's calendar.ics: $!");
		ProgressMade(3, $Total, $ProgressWin);
		if (-e $DP_EVO_Link)
		{
			if (-l $DP_EVO_Link && readlink($DP_EVO_Link) =~ /dayplanner$/)
			{
				unlink($DP_EVO_Link);
			}
			else
			{
				DPIntWarn("Failed to enable evolution compat. $DP_EVO_Link was not a symlink that pointed to a day planner file.");
				DP_DestroyProgressWin($ProgressWin);
				return false;
			}
		}
		ProgressMade(4, $Total, $ProgressWin);
		symlink($FindBin::RealBin.'/dayplanner',$DP_EVO_Link) or DPIntWarn("Unable to symlink day planner to evolution: $!");
		ProgressMade(5, $Total, $ProgressWin);
		DP_DestroyProgressWin($ProgressWin);
		return true;
	}
	DPIntWarn("EvoCompat_Enable() called when evo compat was already enabled");
	return false;
}

# Purpose: Disable Evolution compat moe.
# Usage: EvoCompat_Disable();
sub EvoCompat_Disable
{
	my $EVO_Target_Link = $ENV{HOME}.'/.evolution/calendar/local/system/calendar.ics';
	my $DP_EVO_Link = $ENV{HOME}.'/.local/bin/evolution';
	if (-e $DP_EVO_Link && readlink($DP_EVO_Link) =~ /dayplanner$/)
	{
		unlink($DP_EVO_Link) or DPIntWarn("Unlinking $DP_EVO_Link failed: $!");
	}
	if (-l $EVO_Target_Link)
	{
		unlink($EVO_Target_Link) or DPIntWarn("Unlinking $EVO_Target_Link failed: $!");
		if (-e $EVO_Target_Link.'.dayplanner_evocompat_backup')
		{
			runtime_use('File::Copy qw(move)');
			move($EVO_Target_Link.'.dayplanner_evocompat_backup',$EVO_Target_Link) or DPIntWarn("Dropping .dayplanner_evocompat_backup failed: $!");
		}
	}
}

# =============================================================================
# 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)/) {
		runtime_use('File::Basename');
		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
	CloseIPC();
	# Exit peacefully
	exit(0);
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# HTML/PHP export functions 
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# 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($i18n->get_advanced('Day Planner for %(date)', { date => $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($i18n->get("Tools")) . ": <a href='$Links{yearindex}'>" . HTML_Encode($i18n->get("View the main page")) . "</a><br/>\n";
#	} elsif(not defined($NonDateMode) or not $NonDateMode eq "M") {
		$Menu .= HTML_Encode($i18n->get("Tools")) . ": <a href='$Links{yearindex}'>" . HTML_Encode($i18n->get("Change to another year")) . "</a> (" . HTML_Encode($i18n->get_advanced("current: %(year)", { year => $Year })) . ") - </a><br />\n";
		$Menu .= HTML_Encode($i18n->get("Months")) .
			": <a href='$Links{1}'>" . HTML_Encode($i18n->get_month(1)) .
			"</a> - <a href='$Links{2}'>" . HTML_Encode($i18n->get_month(2)).
			"</a> - <a href='$Links{3}'>" . HTML_Encode($i18n->get_month(3)) .
			"</a> - \n<a href='$Links{4}'>" . HTML_Encode($i18n->get_month(4)) .
			"</a> - <a href='$Links{5}'>" . HTML_Encode($i18n->get_month(5)) .
			"</a> - <a href='$Links{6}'>" . HTML_Encode($i18n->get_month(6)) .
			"</a> - \n<a href='$Links{7}'>" . HTML_Encode($i18n->get_month(7)) .
			"</a> - <a href='$Links{8}'>" . HTML_Encode($i18n->get_month(8)) .
			"</a> - <a href='$Links{9}'>" . HTML_Encode($i18n->get_month(9)) .
			"</a> - \n<a href='$Links{10}'>" . HTML_Encode($i18n->get_month(10)) .
			"</a> - <a href='$Links{11}'>" . HTML_Encode($i18n->get_month(11)) .
			"</a> - <a href='$Links{12}'>" . HTML_Encode($i18n->get_month(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($i18n->get('Generated by')) .  ' <a href="http://www.day-planner.org/">' . HTML_Encode($i18n->get('Day Planner')) . '</a> ' . HTML_Encode($i18n->get('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);
	my %WrittenEventList;
	open(my $FILE, '>', "$Directory/dp_$Year$Month$Day.html") or do {
		DPIntWarn("Unable to open $Directory/dp_$Year$Month$Day.html for writing: $!");
		DPError($i18n->get_advanced("Unable to open %(file) for writing: %(error)", { file => "$Directory/dp_$Year$Month$Day.html", error => $!}));
		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($i18n->get('Time')) . '</td><td>' . HTML_Encode($i18n->get('Description')) . "</td></tr>\n";
	# Write allday and birthdays
	foreach my $Event (sort (@{$iCalendar->get_timeinfo($Year,$Month,$Day,'DAY')})) {
		if(not($WrittenEventList{$Event}))
		{
			print $FILE '<tr><td></td><td>' . HTML_Encode(GetSummaryString($Event,false,$Year,$Month));
			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";
			$WrittenEventList{$Event} = true;
		}
	}
	# 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)})) {
			if(not($WrittenEventList{$Event}))
			{
				print $FILE "<tr><td>$Time</td><td>" . HTML_Encode(GetSummaryString($Event,false,$Year,$Month));
				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";
			}
			$WrittenEventList{$Event} = true;
		}
	}
	# 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($i18n->get_advanced("Unable to open %(file) for writing: %(error)", { file => "$RawMonthNames{$Month}-$Year.html", error => $!}));
		return(undef);
	};
	print $FILE HTML_Header($Year, $i18n->get_month($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. " . $i18n->get_month($Month) ." $Year") . "</a><br/>\n";
	}
	unless($HadContent) {
		print $FILE '<i>' . HTML_Encode($i18n->get('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);
	}
	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($i18n->get("Unable to open %s for writing: %s"), "$Directory/birthdays.html", $!));
		return(undef);
	};
	print $FILE HTML_Header(undef,$i18n->get("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($i18n->get("Date")) . "</td><td>" . HTML_Encode($i18n->get("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($i18n->get("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($i18n->get_advanced("Unable to open %(file) for writing: %(error)", { file => "$Directory/$Year.html", error => $!}));
		return(undef);
	};
	print $FILE HTML_Header($Year, $Year, 'Y');
	print $FILE HTML_Encode($i18n->get('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($i18n->get_advanced("Unable to open %(file) for writing: %(error)", { file => "$Directory/index.html", error => $!}));
		return(undef);
	};
	print $FILE HTML_Header("", "Day Planner", "M");
	print $FILE HTML_Encode($i18n->get("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($i18n->get_advanced("Unable to open %(file) for writing: %(error)", { file => "$Directory/index.php", error => $!}));
		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) {
		runtime_use('File::Path');
		eval("File::Path::mkpath('$Dir')");
		if($@) {
			DPIntWarn("Unable to mkpath($Dir): $@");
			DPError($i18n->get_advanced("Unable to create the directory %(dir): %(error)", { dir => $Dir, error => $@}));
			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);
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 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} = $i18n->get_advanced("%(name)'s birthday",  { name => $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($i18n->get('Import data'));
	$ImportWindow->set_resizable(0);
	$ImportWindow->set_border_width(12);
	$ImportWindow->set_skip_taskbar_hint(1);
	$ImportWindow->set_skip_pager_hint(1);
	$ImportWindow->set_type_hint('dialog');
	$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($i18n->get('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, $i18n->get('A file'));
	my $FromProgramButton = Gtk2::RadioButton->new ($FromFileButton, $i18n->get('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, $i18n->get('Import data from a file'));
	$Tooltips->set_tip($FromProgramButton, $i18n->get('Import data from various other applications'));
	$Tooltips->set_tip($CancelButton, $i18n->get('Cancel importing and return to Day Planner'));
	$Tooltips->set_tip($OKButton, $i18n->get('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($i18n->get('Preparing'), $i18n->get('Preparing to import'), 0);

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

	# 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;
	}
	ProgressMade($Programs{TotalPrograms}, 4, $ProgressWindow);
	# Detect Orage
	if (-e "$ENV{HOME}/.config/xfce4/orage/orage.ics") {
		$Programs{Orage} = 1;
		$Programs{ImportPossible} = 1;
	}
	ProgressMade($Programs{TotalPrograms}, 5, $ProgressWindow);

	DP_DestroyProgressWin($ProgressWindow);
	if(not $Programs{ImportPossible}) {
		if(not $FirstTime) {
			$MainWindow->show();
			DPInfo($i18n->get('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($i18n->get('Day Planner') . ' - ' . $i18n->get('Import data'));
		# Set the icons
		my @WindowIcons = GetAppIcons();
		if (@WindowIcons)
		{
			$ImportWindow->set_icon_list(@WindowIcons);
		}
	} else {
		$ImportWindow->set_title($i18n->get("Import data"));
		$ImportWindow->set_skip_taskbar_hint(1);
		$ImportWindow->set_skip_pager_hint(1);
	}
	$ImportWindow->set_type_hint('dialog');
	$ImportWindow->set_resizable(0);
	$ImportWindow->set_border_width(12);
	my $ImportVBox = Gtk2::VBox->new();
	$ImportWindow->add($ImportVBox);

	# Create the initial text
	my $LabelText = $FirstTime ? $i18n->get("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.") : $i18n->get('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);
	}
	
	# Orage
	my $OrageButton = Gtk2::CheckButton->new_with_label('Orage');
	$ImportVBox->pack_start($OrageButton,0,0,0);
	$OrageButton->show();
	if($Programs{Orage}) {
		$OrageButton->set_active(1);
	} else {
		$OrageButton->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($i18n->get('Import'));
	my $OKImg = Gtk2::Image->new_from_stock('gtk-open','button');
	$OKButton->set_image($OKImg);
	$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(),$OrageButton->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 $CancelImg = Gtk2::Image->new_from_stock('gtk-cancel','button');
	my $CancelButton = Gtk2::Button->new($i18n->get("Don't import"));
	$CancelButton->set_image($CancelImg);
	$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?, ORAGE?);
sub ImportProgData {
	my($ImportEvolution, $ImportPlan, $ImportGnome, $ImportKorganizer, $ImportOrage) = @_;
	my (@PlanFiles, @EvolutionFiles);

	my $MaxProgress = 1;
	my $CurrentProgress = 1;
	my $Progress = DPCreateProgressWin($i18n->get("Importing..."), $i18n->get("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++;
	}
	# Then do Orage
	if($ImportOrage) {
		ProgressMade($MaxProgress, $CurrentProgress, $Progress, "Orage");
		$iCalendar->addfile("$ENV{HOME}/.config/xfce4/orage/orage.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, $i18n->get("Done"));
	UpdatedData(1);
	DP_DestroyProgressWin($Progress);
	
	$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($i18n->get('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($i18n->get('Export data'), $MainWindow, 'save',
	'gtk-cancel' => 'reject',
	'gtk-save' => 'accept',);
	$ExportWindow->set_current_name($i18n->get('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($i18n->get("Password protect the file (encryption)"));
	#$EncryptionCheckbox->show();
	#$ExportVBox->pack_end($EncryptionCheckbox,0,0,0);
	# Only birthdays checkbox
	#my $BirthdaysOnlyCheckbox = Gtk2::CheckButton->new($i18n->get("Only export birthdays"));
	#$BirthdaysOnlyCheckbox->show();
	#$ExportVBox->pack_end($BirthdaysOnlyCheckbox,0,0,0);

	# Label
	my $ActiveIndex;
	my $FiletypeLabel = Gtk2::Label->new($i18n->get('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)');
	# TRANSLATORS: Selects where to export to
	$Export_Combo->insert_text(1, $i18n->get('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($i18n->get_advanced("There are already files in the directory \"%(dir)\". If you continue any existing exported Day Planner data in that directory will be overwritten. Do you want to continue?", { dir =>  $File}))) {
							return(1);
						} else {
							return(undef);
						}
					}
				}
				return(1);
			} else {
				DPInfo($i18n->get_advanced("\"%(file)\" already exists and is not a directory", { file => $File }));
				return(undef);
			}
		} else {
			return(1);
		}
	} else {
		runtime_use('File::Basename');
		my $FileDirname = dirname($File);
		unless(-w $FileDirname) {
			DPInfo($i18n->get_advanced("\"%(dir)\" is read only. Write permissions are required.", { dir => $FileDirname}));
			return(undef);
		}
		if(-e $File) {
			if(-d $File) {
				DPInfo($i18n->get_advanced("\"%(dir)\" is a directory.", { dir => $File }));
				return(undef);
			} elsif(DPQuestion($i18n->get_advanced("\"%(file)\" already exists. Overwrite?", { file => $File}))) {
				return(1);
			} else {
				return(undef);
			}
		} else {
			return(1);
		}
	}
}

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

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

# Purpose: Work around limitations in older Gtk2 versions that don't have
# 	->set_image on buttons
# Usage: Gtk2_Button_SetImage(BUTTON, IMAGE, LABEL);
# 	Sets LABEL if version is old
sub Gtk2_Button_SetImage {
	my($button,$image,$label) = @_;
	if($Gtk2::VERSION <= 1.070) {
		DPIntWarn("You're running Day Planner using an old version of Gtk2. This version contains certain limitations which will be worked around. This will cause the interface to look sub-optimal, you are encouraged to upgrade to a later version of Gtk2/Gtk2-perl");
		return $button->set_label($i18n->get($label));
	} else {
		return $button->set_image($image);
	}
}	

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

# Purpose: Destroy a progress window created with DPCreateProgressWin()
# Usage: DP_DestroyProgressWin(HASH);
sub DP_DestroyProgressWin
{
	return if not $Gtk2Init;
	my $win = shift;
	$win->{Window}->destroy();
}

# 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 {
	return if not $Gtk2Init;

	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);
	$ProgressHash{Window}->set_type_hint('dialog');
	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 $ProgressHash = shift;
	return if not $Gtk2Init;
	if(defined($ProgressHash)) {		# So that the calling function can just *assume* it has a progressbar
						# even when it doesn't
		my $Bar = $ProgressHash->{ProgressBar};
		$Bar->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 if not $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 = GetUpcomingEventsString($iCalendar);
	# Don't update the widget if the text hasn't changed
	if(not $UpcomingEventsWidget->get_buffer eq $NewUpcoming) {
		$UpcomingEventsBuffer->set_text($NewUpcoming);
	}
}

# Purpose: Delete the event currently selected in the eventlist
# Usage: DeleteEvent();
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($i18n->get("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->set_title($i18n->get('Day Planner'));
		$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->set_title($i18n->get('Day Planner'));
		$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->set_title($i18n->get('Day Planner'));
		$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]);
	$Dialog->set_title($i18n->get('Day Planner'));
	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($i18n->get('Some files could not be saved correctly. Quit anyway?'))) {
			$MainWindow->show();
			return(1);
		}
	}
	WriteStateFile($SaveToDir, 'state.conf');
	
	Gtk2->main_quit;
	CloseIPC();
	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
		}
	}
}

sub GetSummaryString
{
	return P_GetSummaryString($CalendarWidget,$iCalendar,@_);
}

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

# HELPER FUNCTIONS

# Purpose: Get a Gtk2::Pixbuf array of icons
# Usage: array = GetAppIcons();
# TODO: Make the the array global so that we don't have to re-create this multiple times for other windows.
sub GetAppIcons
{
	my $maxImages = 4;
	my $totalImages = 0;
	my @ret;
	foreach my $img (qw(dayplanner-48x48.png dayplanner-32x32.png dayplanner-24x24.png dayplanner-16x16.png dayplanner.png dayplanner_HC48.png dayplanner_HC24.png dayplanner_HC16.png dayplanner-about.png))
	{
		my $path = DetectImage($img);
		next if not $path;
		my $pbuf = Gtk2::Gdk::Pixbuf->new_from_file($path);
		next if not $pbuf;
		$totalImages++;
		push(@ret,$pbuf);
		if ($totalImages <= $maxImages)
		{
			last;
		}
	}
	if(not @ret)
	{
		DPIntWarn('Failed to detect any images or icons. Strange or broken install?');
	}
	return(@ret);
}

# Purpose: Make buttons for add/edit active or inactive
# Usage: EventListToggle();
sub EventListToggle
{
	if(defined [$EventlistWidget->get_selected_indices]->[0])
	{
		$ToolbarEditButton->set_sensitive(1);
		$ToolbarDeleteButton->set_sensitive(1);
		$MenuEditEntry->set_sensitive(1);
		$MenuDeleteEntry->set_sensitive(1);
	}
	else
	{
		$ToolbarEditButton->set_sensitive(0);
		$ToolbarDeleteButton->set_sensitive(0);
		$MenuEditEntry->set_sensitive(0);
		$MenuDeleteEntry->set_sensitive(0);
	}
}

# Purpose: Create the details widget
# Usage: my $ExpanderWidget = CreateDetailsWidget();
sub CreateDetailsWidget {
	my $FT_Expander = Gtk2::Expander->new($i18n->get('Show advanced settings'));
	$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($i18n->get('Hide advanced settings'));
			} else {
				$FT_Expander->set_label($i18n->get('Show advanced settings'));
			}
		});
	return($FT_Expander);
}

# Purpose: Add a date to a text field
# Usage: AddToField(ENTRY, YEAR, MONTH, DAY);
sub AddToField {
	my($Entry,$Year,$Month,$Day) = @_;

	$Month = AppendZero($Month);
	$Day = AppendZero($Day);
	# First get the text
	my $FieldText = $Entry->get_text();
	# Then add to the text
	if($FieldText =~ /\S/) {
		$FieldText .= ", ";
	}
	$FieldText .= "$Day.$Month.$Year";
	$Entry->set_text($FieldText);
}

# Purpose: Remove a date from a text field (more tricky)
# Usage: RemoveFromField(ENTRY, YEAR, MONTH, DAY);
sub RemoveFromField {
	my($Entry,$EventYear,$EventMonth,$EventDay) = @_;
	# First get the text
	my $FieldText = $Entry->get_text();
	# Then redo it
	my $NewField = "";
	foreach my $String (split(/[,|\s]+/, $FieldText)) {
		my $Year = $String;
		my $Month = $String;
		my $Day = $String;
		$Year =~ s/^\d+\.\d+\.(\d\d\d\d)$/$1/;
		$Month =~ s/^\d+\.(\d+)\.\d\d\d\d$/$1/;
		$Day =~ s/^(\d+).*$/$1/;
		$Month =~ s/^0//;
		$Day =~ s/^0//;
		if(not $Year eq $EventYear or not $Month eq $EventMonth or not $Day eq $EventDay) {
			if($NewField) {
				$NewField .= ", ";
			}
			$NewField .= "$Day.$Month.$Year"
		}
	}
	$Entry->set_text($NewField);
}

# Purpose: Popup a calendar to select one or more dates from
# Usage: \&PopupDateSel(TEXT ENTRY OBJECT, CALENDAR BUTTON, MODE, DEFAULT_DATE?);
# 	MODE is one of:
#	 	SINGLE - Only allows a single date to be selected
# 		MULTI - Allows an infinite number of dates to be selected
# 	DEFAULT_DATE is one of:
# 		undef - Use the first entry in the entry field
# 		CURRENT - Use todays date
sub PopupDateSel
{
	my $TextEntry = shift;
	my $CalButton = shift;
	$CalButton->set_sensitive(0);
	my $Mode = shift;
	my $Default_Date = shift;

	# This is used to indicate when we are changing months,
	# so that changing month doesn't inadvertedly change the date too.
	my $ChangingNow;

	# Create the window and stuff inside the window
	my $Window = Gtk2::Window->new();
	$Window->set_deletable(0);
	$Window->set_modal(1);
	$Window->set_skip_taskbar_hint(1);
	$Window->set_skip_pager_hint(1);
	$Window->set_title($i18n->get('Calendar'));
	$Window->set_position('mouse');
	$Window->set_resizable(0);
	$Window->set_border_width(5);
	$Window->set_transient_for($MainWindow);
	$Window->set_type_hint('dialog');
	$Window->signal_connect('destroy' => sub {
			$Window->destroy();
			$CalButton->set_sensitive(1);
			$TextEntry->grab_focus();
		});
	$Window->signal_connect('delete-event' => sub {
			$Window->destroy();
			$CalButton->set_sensitive(1);
			$TextEntry->grab_focus();
		});
	my $VBox = Gtk2::VBox->new();
	$Window->add($VBox);
	my $Calendar = Gtk2::Calendar->new();
	$VBox->pack_start($Calendar,0,0,0);
	my $ButHBox = Gtk2::HBox->new();
	$VBox->pack_end($ButHBox,0,0,0);
	my $CloseButton = Gtk2::Button->new_from_stock('gtk-close');
	$CloseButton->signal_connect(clicked => sub {
			$Window->destroy();
			$CalButton->set_sensitive(1);
			$TextEntry->grab_focus();
		});
	$ButHBox->pack_end($CloseButton,0,0,0);
	# Handle changes to the calendar
	my %SelHash;
	# Select the date in the entry field
	if(defined($Default_Date) and $Default_Date eq 'CURRENT') {
		my ($year, $month, $day) = $CalendarWidget->get_date();
		$Calendar->select_month($month, $year);
	} else {
		my $CurrDate = ParseEntryField($TextEntry);
		if(@{$CurrDate}) {
			my($SelYear,$SelMonth,$SelDay) = ParseDateString($CurrDate->[0]);
			$SelMonth--;
			$Calendar->select_month($SelMonth,$SelYear);
			$Calendar->select_day($SelDay);
		}
	}

	# Get the dates
	$Calendar->signal_connect('month-changed' => sub {
			# If ChangingNow is undef that means we just
			# got created, so initialize it to zero instead of one
			# so that selecting days work properly immedietly.
			if(not defined $ChangingNow) {
				$ChangingNow = 0;
			} else {
				$ChangingNow = 1;
			}
			my $DateRef = ParseEntryField($TextEntry);
			my ($CurrYear, $CurrMonth, $CurrDay) = $Calendar->get_date();
			$CurrMonth++;	# 1-12 not 0-11
			%SelHash = ();
			$Calendar->clear_marks();
			foreach my $Date (@{$DateRef}) {
				my($Year,$Month,$Day) = ParseDateString($Date);
				if($Year eq $CurrYear and $Month eq $CurrMonth) {
					$SelHash{$Day} = 1;
					$Calendar->mark_day($Day);
				}
			}
			# If we're in single mode, then make our day-selected handler do some work aswell (but only when $ChangingNow)
			if($Mode eq 'SINGLE' and $ChangingNow)
			{
				$Calendar->signal_emit('day-selected');
			}
		});
	# Emit a day-selected event on year change in single mode.
	$Calendar->signal_connect('prev-year', => sub { 
			if ($Mode eq 'SINGLE')
			{
				$Calendar->signal_emit('day-selected');
			}
		});
	$Calendar->signal_connect('next-year', => sub { 
			if ($Mode eq 'SINGLE')
			{
				$Calendar->signal_emit('day-selected');
			}
		});
	# Set the handler for clicking on a date
	# This will add and remove dates from the field
	$Calendar->signal_connect('day-selected' => sub 
		{
			# ChangingNow is used for handling of windows that are not in SINGLE mode
			if ($ChangingNow)
			{
				$ChangingNow = 0;
				return;
			}
			my $Selected = $Calendar->selected_day();
			my ($CurrYear, $CurrMonth, $CurrDay) = $Calendar->get_date();
			$CurrMonth++;	# Month is 0-11 not 1-12. We want 1-12
			if($SelHash{$Selected} && $Mode eq 'MULTI') {
				$Calendar->unmark_day($Selected);
				delete($SelHash{$Selected});
				RemoveFromField($TextEntry,$CurrYear,$CurrMonth,$CurrDay);
			} else {
				if($Mode eq 'SINGLE') {
					$Calendar->clear_marks();
					%SelHash = ();
					$TextEntry->set_text('');
				}
				$Calendar->mark_day($Selected);
				$SelHash{$Selected} = true;
				AddToField($TextEntry,$CurrYear,$CurrMonth,$CurrDay);
			}
		});
	# On double-click in a single-mode window we close.
	$Calendar->signal_connect('day-selected-double-click' => sub {
			if($Mode eq 'SINGLE') {
				$Calendar->signal_emit('day-selected');
				$CloseButton->signal_emit('clicked');
			}
		});

	# Emit a month-changed signal to initialize fields
	$Calendar->signal_emit('month-changed');

	# Let ESC close the window
	$Window->signal_connect('key_release_event' => \&EscapeKeyHandler);

	# Show it all
	$Window->show_all();
}

# Purpose: Create the advanced settings widget
# Usage: my $ExpanderWidget = CreateAdvancedWidget(RRULE?,EXDATE?);
# 	RRULE is an RRULE hash as returned by DP::iCalendars ->get_RRULE.
# 	It can be undef.
sub CreateAdvancedWidget {
	my $RRULE = shift;
	my $EXDATE = shift;
	my $UNTIL = shift;
	# Create the expander and set up signal handlers
	my $FT_Expander = Gtk2::Expander->new($i18n->get('Show advanced settings'));
	$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($i18n->get('Hide advanced settings'));
			} else {
				$FT_Expander->set_label($i18n->get('Show advanced settings'));
			}
		});
	# Create the vbox to use within the expander
	my $ExpanderVBox = Gtk2::VBox->new();
	$ExpanderVBox->show();
	$FT_Expander->add($ExpanderVBox);
	# Create the recurrance checkbutton
		# HBox for it
	my $RecurHBox = Gtk2::HBox->new();
	$RecurHBox->show();
	$ExpanderVBox->pack_start($RecurHBox,0,0,0);
		# Checkbutton for it
	my $RecurCheckButton = Gtk2::CheckButton->new($i18n->get('Repeat event every:') . ' ');
	$RecurCheckButton->show();
	$RecurHBox->pack_start($RecurCheckButton,0,0,0);
	my $Table;		# For use later, needs to be declared here to be used in the RecurCheckButton callback
		# The combo for it
	my $Recur_Combo = Gtk2::ComboBox->new_text();
	# TRANSLATORS: Context: Repeat event every: THIS STRING
	$Recur_Combo->insert_text(0, $i18n->get('day'));
	# TRANSLATORS: Context: Repeat event every: THIS STRING
	$Recur_Combo->insert_text(1, $i18n->get('week'));
	# TRANSLATORS: Context: Repeat event every: THIS STRING
	$Recur_Combo->insert_text(2, $i18n->get('month'));
	# TRANSLATORS: Context: Repeat event every: THIS STRING
	$Recur_Combo->insert_text(3, $i18n->get('year'));
	$Recur_Combo->set_active(0);
	$Recur_Combo->show();
	$Recur_Combo->set_sensitive(0);
		# Checkbutton callback
	$RecurCheckButton->signal_connect('toggled' => sub {
			if($RecurCheckButton->get_active) {
				$Recur_Combo->set_sensitive(1);
				$Table->set_sensitive(1);
			} else {
				$Recur_Combo->set_sensitive(0);
				$Table->set_sensitive(0);
			}
		});
	$RecurHBox->pack_start($Recur_Combo,0,0,0);
		# Recurrance Table
	$Table = Gtk2::Table->new(2,4);
	$Table->set_sensitive(0);
	$Table->show();
	# Create the recurrance ends checkbutton
		# This is exceedingly ugly. But...works
	my $SecondIndentLabel = Gtk2::Label->new('    ');
	$SecondIndentLabel->show();
	$Table->attach_defaults($SecondIndentLabel,0,1,0,1);
		# Checkbutton for it
		# TRANSLATORS: The label in front of the entry box where you enter the date when a recurring event should stop recurring.
		my $StopAtCheckButton = Gtk2::CheckButton->new($i18n->get('Until:') . ' ');
	$StopAtCheckButton->show();
	$Table->attach_defaults($StopAtCheckButton,1,2,0,1);
		# The entry field
	my $StopAt_Entry = Gtk2::Entry->new();
	$StopAt_Entry->show();
	$StopAt_Entry->set_sensitive(0);
	$Table->attach_defaults($StopAt_Entry,2,3,0,1);
		# The calendar button
	my $StopAt_CalButton = Gtk2::Button->new();
	my $SecondCalImage = Gtk2::Image->new_from_file(DetectImage('x-office-calendar.png','dayplanner-16x16.png'));
	Gtk2_Button_SetImage($StopAt_CalButton,$SecondCalImage,'Calendar');
	$StopAt_CalButton->show();
	$StopAt_CalButton->set_sensitive(0);
	$StopAt_CalButton->signal_connect('clicked' => sub { PopupDateSel($StopAt_Entry,$StopAt_CalButton,'SINGLE','CURRENT')});
	$Table->attach_defaults($StopAt_CalButton,3,4,0,1);
		# CheckButton callback
	$StopAtCheckButton->signal_connect('toggled' => sub {
			if($StopAtCheckButton->get_active) {
				$StopAt_Entry->set_sensitive(1);
				$StopAt_CalButton->set_sensitive(1);
			} else {
				$StopAt_Entry->set_sensitive(0);
				$StopAt_CalButton->set_sensitive(0);
			}
		});
	# Create the recurrance exception checkbutton
		# HBox for it
	$ExpanderVBox->pack_start($Table,0,0,0);
		# This is exceedingly ugly. But...works
	my $IndentLabel = Gtk2::Label->new('    ');
	$IndentLabel->show();
	$Table->attach_defaults($IndentLabel,0,1,1,2);
		# Checkbutton for it
		# TRANSLATORS: Don't recur the event on the dates specified next to this string
	my $ExceptCheckButton = Gtk2::CheckButton->new($i18n->get('But not on:') . ' ');
	$ExceptCheckButton->show();
	$Table->attach_defaults($ExceptCheckButton,1,2,1,2);
		# The entry field
	my $ExceptEntry = Gtk2::Entry->new();
	$ExceptEntry->show();
	$ExceptEntry->set_sensitive(0);
	$Table->attach_defaults($ExceptEntry,2,3,1,2);
		# The calendar button
	my $CalButton = Gtk2::Button->new();
	my $CalImage = Gtk2::Image->new_from_file(DetectImage('x-office-calendar.png','dayplanner-16x16.png'));
	Gtk2_Button_SetImage($CalButton,$CalImage,'Calendar');
	$CalButton->show();
	$CalButton->set_sensitive(0);
	$CalButton->signal_connect('clicked' => sub { PopupDateSel($ExceptEntry,$CalButton,'MULTI','CURRENT')});
	$Table->attach_defaults($CalButton,3,4,1,2);
		# CheckButton callback
	$ExceptCheckButton->signal_connect('toggled' => sub {
			if($ExceptCheckButton->get_active) {
				$ExceptEntry->set_sensitive(1);
				$CalButton->set_sensitive(1);
			} else {
				$ExceptEntry->set_sensitive(0);
				$CalButton->set_sensitive(0);
			}
		});
		
		# Get RRULE information if provided and set it
	if($RRULE) {
		$FT_Expander->signal_emit('activate');
		my $FREQ = $RRULE->{FREQ};
		$RecurCheckButton->set_active(1);
		$Table->set_sensitive(1);
		if($FREQ eq 'DAILY') {
			$Recur_Combo->set_active(0);
		} elsif($FREQ eq 'WEEKLY') {
			$Recur_Combo->set_active(1);
		} elsif($FREQ eq 'MONTHLY') {
			$Recur_Combo->set_active(2);
		} elsif($FREQ eq 'YEARLY') {
			$Recur_Combo->set_active(3);
		}
		# Get UNTIL information if provided and set it
		if($RRULE->{UNTIL}) {
			$StopAtCheckButton->set_active(1);
			AddToField($StopAt_Entry,iCal_ParseDateTime($RRULE->{UNTIL}));
		}
		# Get EXDATE information if provided and set it
		if(@{$EXDATE}) {
			$ExceptCheckButton->set_active(1);
			foreach my $entry (sort @{$EXDATE}) {
				AddToField($ExceptEntry,iCal_ParseDateTime($entry));
			}
		}
	}

	# Create the entry for the fulltext.
	# The caller should add text and such as needed
	my $FulltextEntry = Gtk2::TextView->new();
	$FulltextEntry->set_editable(1);
	$FulltextEntry->set_wrap_mode('word-char');
	$FulltextEntry->show();
	$FulltextEntry->set_accepts_tab(0);
	my $FulltextWindow = Gtk2::ScrolledWindow->new;
	$FulltextWindow->set_policy('automatic', 'automatic');
	$FulltextWindow->add($FulltextEntry);
	$FulltextWindow->show();
	my $FTFrame = Gtk2::Frame->new();
	$FTFrame->show();
	$FTFrame->add($FulltextWindow);
	$FTFrame->set_label($i18n->get('Detailed description'));
	$ExpanderVBox->pack_start($FTFrame,0,0,0);
	return($FT_Expander,$FulltextEntry,$Recur_Combo,$ExceptEntry,$StopAt_Entry);
}

# 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 = $i18n->get_ampmstring('AM');
		} else {
			$Prefix = $i18n->get_ampmstring('PM');
		}
		($Hour,$Minute) = $i18n->AMPM_To24("$Hour:$Minute $Prefix", 1);
	}
	return("$Hour:$Minute");
}

# Purpose: Get a formatted RRULE from the combo box.
# Usage: my $RRULE = GetRRULEFromCombo(WIDGET_HASH);
sub GetRRULEFromCombo {
	my $Hash = shift;
	my $combo = $Hash->{RecurCombo};
	if(not $combo->is_sensitive) {
		return(undef);
	}
	my $RRULE;
	# 0 == day
	# 1 == week
	# 2 == month
	# 3 == year
	my $ActiveIndex = $combo->get_active;
	if($ActiveIndex == 0) {
		$RRULE = 'FREQ=DAILY';
	} elsif ($ActiveIndex == 1) {
		$RRULE = 'FREQ=WEEKLY';
	} elsif ($ActiveIndex == 2) {
		$RRULE = 'FREQ=MONTHLY';
	} elsif ($ActiveIndex == 3) {
		$RRULE = 'FREQ=YEARLY;INTERVAL=1';
	}
	# Get UNTIL
	my $Until = GetDateFromEntry($Hash->{StopAt_Entry});
	if($Until) {
		$RRULE .= ";UNTIL=$Until";
	}
	return($RRULE);
}

# Purpose: Get a list of formatted EXDATE entries from the exception entry box
# Usage: my $List = GetExceptionsFromEntry(ENTRY WIDGET);
sub GetExceptionsFromEntry {
	my $Entry = shift;
	if (not $Entry->is_sensitive) {
		return([]);
	}
	my $EntryArray = ParseEntryField($Entry);
	my @ReturnArray;
	foreach my $Date (@{$EntryArray}) {
		my($Year,$Month,$Day) = ParseDateString($Date);
		$Month = AppendZero($Month);
		$Day = AppendZero($Day);
		push(@ReturnArray,"$Year$Month$Day");
	}
	return(\@ReturnArray);
}

# Purpose: Get the until entry from the until entry box
# Usage: my $UntilEntry = GetDateFromEntry(ENTRY WIDGET);
sub GetDateFromEntry {
	my $Entry = shift;
	if (not $Entry->is_sensitive) {
		return(undef);
	}
	my $EntryArray = ParseEntryField($Entry);
	my @ReturnArray;
	foreach my $Date (@{$EntryArray}) {
		my($Year,$Month,$Day) = ParseDateString($Date);
		$Month = AppendZero($Month);
		$Day = AppendZero($Day);
		if(wantarray()) {
			return($Year,$Month,$Day);
		} else {
			return("$Year$Month$Day");
		}
	}
	return(undef);
}

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

	if($i18n->get_clocktype() == 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($i18n->get_clocktype() == 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 = $i18n->AMPM_From24($Time);
		my $MSTime = $i18n->AMPM_From24($Time);
		my $Suffix = $i18n->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, $i18n->get_ampmstring('AM'));
		$AMPM->insert_text(1, $i18n->get_ampmstring('PM'));
		$AMPM->show();
		# Set the initial value
		if($Suffix eq $i18n->get_ampmstring('AM')) {
			$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 widgets for selecting the date
# Usage: my ($DateTable, $DateEntry) = DateSelection(UID?);
sub DateSelection {
	my $UID = shift;

	my $Table = Gtk2::Table->new(2,1);
	$Table->show();
		# The entry field
	my $Entry = Gtk2::Entry->new();
	$Entry->show();
	$Table->attach_defaults($Entry,0,1,0,1);
		# The calendar button
	my $CalButton = Gtk2::Button->new();
	my $CalImage = Gtk2::Image->new_from_file(DetectImage('x-office-calendar.png','dayplanner-16x16.png'));
	Gtk2_Button_SetImage($CalButton,$CalImage,'Calendar');
	$CalButton->show();
	$CalButton->signal_connect('clicked' => sub { PopupDateSel($Entry,$CalButton,'SINGLE')});
	$Table->attach_defaults($CalButton,1,2,0,1);
	
	# If we have an UID then get it from that, if not then get it from the calendar widget
	if(defined($UID) and not $UID eq 'NULL') {
		my $UIDObj = $iCalendar->get_info($UID);
		# Get date information from the UID
		AddToField($Entry,iCal_ParseDateTime($UIDObj->{DTSTART}))
	} else {
		my ($EventYear, $EventMonth, $EventDay) = $CalendarWidget->get_date();$EventMonth++;
		AddToField($Entry,$EventYear,$EventMonth,$EventDay);
	}
	
	return($Table,$Entry);
}

# Purpose: Create the window that will contain the event editor
# Usage: my ($Window, $VBox_HBoxContainer, $OKButton, $CancelButton) = CreateEventContainerWin(TITLE, OK_BUTTON_TYPE, UID);
#
# TITLE is the title of the window.
# OK_BUTTON_TYPE is the Gtk2::Stock ID to use for the button
# UID is the UID of the event. This can be undef.
sub CreateEventContainerWin {
	# Get the options passed to the sub
	my ($WindowTitle, $OkButtonType, $UID) = @_;
	
	# Get the date
	my ($EventYear, $EventMonth, $EventDay);
	# If we have an UID then get it from that, if not then get it from the calendar widget
	if($UID) {
		my $UIDObj = $iCalendar->get_info($UID);
		# Get date information from the UID
		($EventYear, $EventMonth, $EventDay) = iCal_ParseDateTime($UIDObj->{DTSTART});
		$EventMonth =~ s/^0//;	# Drop leading zero from event month.
	} else {
		($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_type_hint('dialog');
	# This doesn't work. According to the people in #gtk-perl this is due to Gtk2's lack
	# of width-for-height. But might be implemented in later releases of gtk2.
	# For now I leave it commented out. It looks fine for English but doesn't look good
	# for languages using longer strings.
	#$AddEventBox->set_size_request(300,-1);
	# This doesn't work either. It "works" if the window is resizable. But then when the
	# expander is closed it looks like crap. It has no effect if the window isn't resizeable.
	#$AddEventBox->set_default_size(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();
	
	# ==================================================================
	# 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);
}

# MAIN FUNCTIONS

# 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($i18n->get('Add an Event'), 'gtk-add');
	# HBox
	my $SelectorHBox = Gtk2::HBox->new();
	$VBox_HBoxContainer->pack_start($SelectorHBox,0,0,0);
	$SelectorHBox->show();
	# Label
	my $EventTypeLabel = Gtk2::Label->new($i18n->get('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, $i18n->get('Normal'));
	# TRANSLATORS: Context: Event type: THIS STRING
	$EventType_Combo->insert_text(1, $i18n->get('All day'));
	# TRANSLATORS: Context: Event type: THIS STRING
	$EventType_Combo->insert_text(2, $i18n->get('Birthday'));
	$SelectorHBox->pack_start($EventType_Combo,0,0,0);
	$EventType_Combo->show();

	# Create the tooltips widget
	my $Tooltips = Gtk2::Tooltips->new();
	$Tooltips->enable();
	# Set the tooltips
	$Tooltips->set_tip($OKButton, $i18n->get('Add this event'));
	$Tooltips->set_tip($CancelButton, $i18n->get('Discard this event'));
	
	# Create the widgets for the normal event selection
	my ($NormalEvent) = NormalEventWindow('NULL', $OKButton, $VBox_HBoxContainer, $Window);
	# Create the widgets for the all day event selection
	my ($AllDayEvent) = AllDayEventWindow('NULL', $VBox_HBoxContainer, $Window);
	# Create the widgets for the birthday event selection
	my ($BirthdayEvent) = 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) {
				$BirthdayEvent->{MainVBox}->hide();
				$AllDayEvent->{MainVBox}->hide();
				$NormalEvent->{MainVBox}->show();
				$OKButton->signal_handler_disconnect($OKSignal) if defined($OKSignal);
				$OKSignal = $OKButton->signal_connect('clicked' => sub {
					NormalEvent_OK($Window, $NormalEvent, \%TimeHash, 0);
				});
			} elsif ($ActiveIndex == 1) {
				$NormalEvent->{MainVBox}->hide();
				$BirthdayEvent->{MainVBox}->hide();
				$AllDayEvent->{MainVBox}->show();
				$OKButton->signal_handler_disconnect($OKSignal) if defined($OKSignal);
				$OKSignal = $OKButton->signal_connect('clicked' => sub {
					AllDayEvent_OK($Window, $AllDayEvent, \%TimeHash, 0)});
			} elsif ($ActiveIndex == 2) {
				$NormalEvent->{MainVBox}->hide();
				$AllDayEvent->{MainVBox}->hide();
				$BirthdayEvent->{MainVBox}->show();
				$OKButton->signal_handler_disconnect($OKSignal) if defined($OKSignal);
				$OKSignal = $OKButton->signal_connect('clicked' => sub {
					AllDayEvent_OK($Window, $BirthdayEvent, \%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 = $i18n->AMPM_To24($EventlistWidget->{data}[$Selected][1]);
	my $Tooltips = Gtk2::Tooltips->new();
	$Tooltips->enable();
	my $UIDObj = $iCalendar->get_info($EventUID);
	# Get date information from the UID
        my ($EventYear, $EventMonth, $EventDay) = iCal_ParseDateTime($UIDObj->{DTSTART});
	# Get date information from the calendar, we check if it differs with the one from
	# the UID
	my $Differs = 0;
	if($UIDObj->{RRULE}) {
		$Differs = 1;
	}
	my %TimeHash = (
		EventYear => $EventYear,
		EventMonth => $EventMonth,
		EventDay => $EventDay,
		OldEventYear => $EventYear,
		OldEventMonth => $EventMonth,
		OldEventDay => $EventDay,
		OldEventTime => $Time,
		Differs => $Differs,
	);
		
	# 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($i18n->get('Editing an event'), 'gtk-ok',$EventUID);
		# Create the widgets for the normal event selection
		my ($NormalEvent) = NormalEventWindow($EventUID, $OKButton, $VBox_HBoxContainer, $Window);
		$OKButton->signal_connect('clicked' => sub {
				NormalEvent_OK($Window, $NormalEvent, \%TimeHash, $EventUID);
		});
		
		# Show the default widget (Normal event)
		$NormalEvent->{MainVBox}->show();
	} elsif ($Type eq 'bday') {
		# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		# BIRTHDAY EVENT
		# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		($Window, $VBox_HBoxContainer, $OKButton, $CancelButton) = CreateEventContainerWin($i18n->get('Editing a birthday'), 'gtk-ok',$EventUID);
		
		my ($BirthdayEvent) = BirthdayEventWindow($EventUID, $VBox_HBoxContainer, $Window);
		$OKButton->signal_connect('clicked' => sub { AllDayEvent_OK($Window, $BirthdayEvent, \%TimeHash, $EventUID, 1)});
		# Show the main vbox
		$BirthdayEvent->{MainVBox}->show();
	} elsif ($Type eq 'allday') {
		# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		# ALL DAY EVENT
		# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		# Get the text string to use (it differs if we're editing on another date than
		# the selected

		($Window, $VBox_HBoxContainer, $OKButton, $CancelButton) = CreateEventContainerWin($i18n->get('Editing an all day event'), 'gtk-ok',$EventUID);
		my ($AllDayEvent) = AllDayEventWindow($EventUID, $VBox_HBoxContainer, $Window);
		$AllDayEvent->{MainVBox}->show();
		$OKButton->signal_connect('clicked' => sub { AllDayEvent_OK($Window, $AllDayEvent, \%TimeHash, $EventUID, 0)});
	} elsif ($Type eq 'holiday') {
		DPInfo(sprintf($i18n->get("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, $i18n->get('Discard changes'));
	$Tooltips->set_tip($OKButton, $i18n->get('Accept changes'));
			
	# Show the window
	$Window->show();
}

# NORMAL EVENT HANDLING FUNCTIONS

# Purpose: Create the widgets for editing a normal event
# Usage: my $NormalEvent = 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,$EventRRULE,$EventEXDATE);
	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 = '09: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
		($EventYear, $EventMonth, $EventDay, $EventTime) = iCal_ParseDateTime($EventHash->{DTSTART});
		$EventSummary = GetSummaryString($EventUID);
		$EventFulltext = $EventHash->{DESCRIPTION};
		$EventRRULE = $iCalendar->get_RRULE($EventUID);
		$EventEXDATE = $iCalendar->get_exceptions($EventUID);
		($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,3);
	$ContentTable->show();
	$MainVBox->pack_start($ContentTable,1,1,0);
	
	# ==================================================================
	# ADD THE DATE/TIME/SUMMARY BOXES
	# ==================================================================
	
	# Create the time label
	my $TimeLabel = Gtk2::Label->new($i18n->get('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 date label
	my $DateLabel = Gtk2::Label->new($i18n->get('Date:'));
	$DateLabel->show();
	$ContentTable->attach_defaults($DateLabel, 0,1,1,2);
	# Date  entry box
	my ($DateTable, $DateEntry) = DateSelection($EventUID);
	# Attach them to our main widget
	$ContentTable->attach_defaults($DateTable, 1,2,1,2);
	
	# Create the summary label
	my $SummaryLabel = Gtk2::Label->new($i18n->get('Description:'));
	$SummaryLabel->show();
	$ContentTable->attach_defaults($SummaryLabel, 0,1,2,3);

	# 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,2,3);
	$Tooltips->set_tip($SummaryEntry, $i18n->get('Enter a description of the event here'));
	
	# ==================================================================
	# ADD THE EXTENDED ENTRY
	# ==================================================================
	my $FulltextWindow;
	
	my ($FT_Expander, $FulltextEntry,$RecurCombo,$ExceptEntry,$StopAt_Entry) = CreateAdvancedWidget($EventRRULE,$EventEXDATE);
	$MainVBox->pack_start($FT_Expander,0,0,0);
	# 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);
		if(not $FT_Expander->get_expanded) {
			$FT_Expander->signal_emit('activate');
		}
	}

	# Create a hash containing the widgets that the caller might need
	# and return it.
	my %ReturnHash;
	$ReturnHash{MainVBox} = $MainVBox;
	$ReturnHash{HourSpinner} = $HourSpinner;
	$ReturnHash{MinuteSpinner} = $MinuteSpinner;
	$ReturnHash{AMPM} = $AMPM;
	$ReturnHash{SummaryEntry} = $SummaryEntry;
	$ReturnHash{FulltextEntry} = $FulltextEntry;
	$ReturnHash{RecurCombo} = $RecurCombo;
	$ReturnHash{ExceptEntry} = $ExceptEntry;
	$ReturnHash{StopAt_Entry} = $StopAt_Entry;
	$ReturnHash{DateEntry} = $DateEntry;
	return(\%ReturnHash);
}

# Purpose: Handle the OK button for the Normal event widget
# Usage: $OKButton->signal_connect('clicked' => sub { NormalEvent_OK($MainWindow, $WidgetHash, \%TimeHash, EventUID)});
#
# EventUID can be undef.
sub NormalEvent_OK {
	my ($MyWindow, $WidgetHash, $TimeHash, $EventUID) = @_;
	my $Error = 0;
	
	# Get the contents
	my $Time = GetTimeFromWidgets($WidgetHash->{HourSpinner},$WidgetHash->{MinuteSpinner}, $WidgetHash->{AMPM});
	my $Summary = $WidgetHash->{SummaryEntry}->get_text;
	my $FulltextBuff = $WidgetHash->{FulltextEntry}->get_buffer;
	my $Fulltext = $FulltextBuff->get_text($FulltextBuff->get_bounds,1);

	# Get variables from the TimeHash
	my $OldEventYear = ${$TimeHash}{OldEventYear};
	my $OldEventMonth = ${$TimeHash}{OldEventMonth};
	my $OldEventDay = ${$TimeHash}{OldEventDay};
	my $OldEventTime = ${$TimeHash}{OldEventTime};
	# Get time
	my($EventYear,$EventMonth,$EventDay) = GetDateFromEntry($WidgetHash->{DateEntry});
	
	# TODO: Add tests for an event that is *identical*
	unless ($Summary) {
		DPError($i18n->get('There is no description of this event. Please enter a description.'));
		$Error = 1;
	}
	if(not $EventYear and not $Error) {
		DPError($i18n->get('The date you have entered is invalid.'));
		$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);
		$EntryHash{RRULE} = GetRRULEFromCombo($WidgetHash);
		foreach my $EXDATE (@{GetExceptionsFromEntry($WidgetHash->{ExceptEntry})}) {
			if(not $EntryHash{EXDATE}) {
				$EntryHash{EXDATE} = [];
			}
			push(@{$EntryHash{EXDATE}}, $EXDATE);
		}

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

# ALL DAY AND BIRTHDAY EVENT HANDLING FUNCTIONS

# Purpose: Create the widgets for editing an all-day event
# Usage: my ($AllDayEventWidget, $SummaryWidget, $DetailsWidget) = AllDayEventWindow(UID, VBOX_WIDGET, MAIN_WINDOW_WIDGET, WIDGET HASHREF);
#
# 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,$EventRRULE,$EventEXDATE);
	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};
		$EventRRULE = $iCalendar->get_RRULE($EventUID);
		$EventEXDATE = $iCalendar->get_exceptions($EventUID);
		($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 DATE/SUMMARY BOXES
	# ==================================================================
	# Create the date label
	my $DateLabel = Gtk2::Label->new($i18n->get('Date:'));
	$DateLabel->show();
	$ContentTable->attach_defaults($DateLabel, 0,1,0,1);
	# Date  entry box
	my ($DateTable, $DateEntry) = DateSelection($EventUID);
	# Attach them to our main widget
	$ContentTable->attach_defaults($DateTable, 1,2,0,1);
	
	# Create the summary label
	my $SummaryLabel = Gtk2::Label->new($i18n->get('Description:'));
	$SummaryLabel->show();
	$ContentTable->attach_defaults($SummaryLabel, 0,1,1,2);

	# Create the summary entry box
	my $SummaryEntry = Gtk2::Entry->new();
	if (defined($EventSummary) and length($EventSummary)) {
		$SummaryEntry->set_text($EventSummary);
	}
	$SummaryEntry->set_activates_default(1);
	$SummaryEntry->show();
	$ContentTable->attach_defaults($SummaryEntry, 1,2,1,2);
	$Tooltips->set_tip($SummaryEntry, $i18n->get('Enter a description of the event here'));
	
	# ==================================================================
	# ADD THE EXTENDED ENTRY
	# ==================================================================
	my $FulltextWindow;
	
	my ($FT_Expander, $FulltextEntry,$RecurCombo,$ExceptEntry,$StopAt_Entry) = CreateAdvancedWidget($EventRRULE,$EventEXDATE);
	$Tooltips->set_tip($FT_Expander, $i18n->get('Additional information'));
	$MainVBox->pack_start($FT_Expander,0,0,0);
	
	# 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);
		# Expand it if it isn't already
		if(not $FT_Expander->get_expanded) {
			$FT_Expander->signal_emit('activate');
		}
	}
	
	my %ReturnHash;
	$ReturnHash{MainVBox} = $MainVBox;
	$ReturnHash{SummaryEntry} = $SummaryEntry;
	$ReturnHash{FulltextEntry} = $FulltextEntry;
	$ReturnHash{RecurCombo} = $RecurCombo;
	$ReturnHash{ExceptEntry} = $ExceptEntry;
	$ReturnHash{StopAt_Entry} = $StopAt_Entry;
	$ReturnHash{DateEntry} = $DateEntry;
	
	return(\%ReturnHash);
}

# 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, $WidgetHash,$TimeHash, $EventUID,$IsBirthday) = @_;
	my $Error = 0;
	
	# Get the contents of the summary
	my $Summary = $WidgetHash->{SummaryEntry}->get_text;

	# Get variables from the TimeHash
	my $OldEventYear = ${$TimeHash}{OldEventYear};
	my $OldEventMonth = ${$TimeHash}{OldEventMonth};
	my $OldEventDay = ${$TimeHash}{OldEventDay};
	# From the widget hash
	my($EventYear,$EventMonth,$EventDay) = GetDateFromEntry($WidgetHash->{DateEntry});
	
	# TODO: Add tests for an event that is *identical* (wouldn't this be a job for DP::iCalendar ?)
	if (not $Summary) {
		DPError($i18n->get('There is no description of this event. Please enter a description.'));
		$Error = 1;
	}
	if(not $EventYear and not $Error) {
		DPError($i18n->get('The date you have entered is invalid.'));
		$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{'X-DP-BORNATDTSTART'} = 'TRUE';
			$EntryHash{RRULE} = 'FREQ=YEARLY';
			$EntryHash{SUMMARY} = $i18n->get_advanced("%(name)'s birthday", { name => $Summary });
		} else {
			$EntryHash{SUMMARY} = $Summary;
			$EntryHash{RRULE} = GetRRULEFromCombo($WidgetHash);
			# Get and add content to the fulltext
			my $FulltextBuff = $WidgetHash->{FulltextEntry}->get_buffer;
			my $Fulltext = $FulltextBuff->get_text($FulltextBuff->get_bounds,1);
			$EntryHash{DESCRIPTION} = $Fulltext;
			# Get exceptions
			foreach my $EXDATE (@{GetExceptionsFromEntry($WidgetHash->{ExceptEntry})}) {
				if(not $EntryHash{EXDATE}) {
					$EntryHash{EXDATE} = [];
				}
				push(@{$EntryHash{EXDATE}}, $EXDATE);
			}
		}
		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: Create the widgets for editing a birthday event
# Usage: my ($BirthdayEventWidget, $SummaryWidget) = BirthdayEventWindow(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 DATE/SUMMARY BOXES
	# ==================================================================
	# Create the date label
	my $DateLabel = Gtk2::Label->new($i18n->get('Born:'));
	$DateLabel->show();
	$ContentTable->attach_defaults($DateLabel, 0,1,0,1);
	# Date  entry box
	my ($DateTable, $DateEntry) = DateSelection($UID);
	# Attach them to our main widget
	$ContentTable->attach_defaults($DateTable, 1,2,0,1);
	
	# Create the summary label
	# TRANSLATORS: Followed by a field where you enter the name of the person whose birthday it is
	my $SummaryLabel = Gtk2::Label->new($i18n->get('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, $i18n->get('Enter the name of the person whose birthday it is here'));
	
	# Create a hash containing the widgets that the caller might need
	# and return it.
	my %ReturnHash;
	$ReturnHash{MainVBox} = $MainVBox;
	$ReturnHash{SummaryEntry} = $SummaryEntry;
	$ReturnHash{DateEntry} = $DateEntry;
	return(\%ReturnHash);
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 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 <eskild at zerodogg dot org>');
	$AboutDialog->set_artists('Jason Holland (original icon and logo)' . "\n" .
					'Andreas Nilsson (small calendar icon)' . "\n" .
					'The Tango Project - http://tango.freedesktop.org/'
				);
	$AboutDialog->set_copyright('Copyright (C) Eskild Hustvedt 2006, 2007, 2008');
	$AboutDialog->set_url_hook(\&LaunchWebBrowser);
	$AboutDialog->set_website('http://www.day-planner.org/');
	# TRANSLATORS: Feel free to localize the application name "Day Planner" to your language.
	#		For instance "Dagsplanleggar" for Norwegian.
	my $AppName = $i18n->get('Day Planner');
	# Use set_program_name instead of set_name in Gtk2 2.14+
	if(Gtk2->CHECK_VERSION(2,14,0)) {
		$AboutDialog->set_program_name($AppName);
	}
	else
	{
		$AboutDialog->set_name($AppName);
	}
	$AboutDialog->set_version($Version);
	# Add revision when we're the SVN version
	if ($VersionName eq 'SVN')
	{
		my $Rev = $RCSRev;
		$Rev =~ s/^.*dayplanner\s+(\d+)\s+.*/$1/;
		$AboutDialog->set_version($Version.' SVN r'.$Rev);
	}
	# 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 3\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 recieved a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.");
	# Logo
	my $LogoImage = DetectImage('dayplanner-about.png','dayplanner-48x48.png','dayplanner-32x32.png','dayplanner-24x24.png', 'dayplanner-16x16.png', 'dayplanner.png','dayplanner_HC48.png','dayplanner_HC24.png', 'dayplanner_HC16.png', );
	if ($LogoImage) {
		my $PixBuf = Gtk2::Gdk::Pixbuf->new_from_file($LogoImage);
		$AboutDialog->set_logo($PixBuf);
	}
	# Translator credits
	# TRANSLATORS: Please write translator credits here, in the form:
	# 				name <email>.
	# 				Separate names with \n.
	if (not $i18n->get('translator-credits') eq 'translator-credits')
	{
		$AboutDialog->set_translator_credits($i18n->get('translator-credits'));
	}
	$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);
	$PreferencesWindow->set_type_hint('dialog');
	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);
	
	# ==================================================================
	# ==================================================================
	# REMINDERS TAB
	# ==================================================================
	# ==================================================================
	
	# Create the vbox
	my $Config_VBox = Gtk2::VBox->new();
	$Config_VBox->show();
	$Notebook->append_page($Config_VBox,$i18n->get('Reminders'));
	$Config_VBox->set_border_width(12);
	
	# 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',
	);
	
	# If the reminder (daemon) should autostart on login or not
	my $AutostartReminder_Checkbox = Gtk2::CheckButton->new_with_label($i18n->get('Start the Day Planner reminder automatically when logging in'));
	$AutostartReminder_Checkbox->show();
	$Config_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($i18n->get('When an event is coming up, remind me:'));
	$NotificationLabel->show();
	$NotificationLabel->set_justify('left');
	$NotificationLabel->set_alignment(0,0);
	$Config_VBox->pack_start($NotificationLabel,0,0,0);
	
	# If the user wants to be notified in advance
	my $NotifyAdvance_CheckBox = Gtk2::CheckButton->new_with_label($i18n->get('One day in advance'));
	$NotifyAdvance_CheckBox->show();
	$Config_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();
	$Config_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($i18n->get('%s minutes'),'10'));
	$TimeSel_Combo->insert_text(1, sprintf($i18n->get('%s minutes'),'20'));
	$TimeSel_Combo->insert_text(2, sprintf($i18n->get('%s minutes'),'30'));
	$TimeSel_Combo->insert_text(3, sprintf($i18n->get('%s minutes'),'45'));
	$TimeSel_Combo->insert_text(4, sprintf($i18n->get('%s hour'),'1'));
	$TimeSel_Combo->insert_text(5, sprintf($i18n->get('%s hours'),'2'));
	$TimeSel_Combo->insert_text(6, sprintf($i18n->get('%s hours'),'4'));
	$TimeSel_Combo->insert_text(7, sprintf($i18n->get('%s hours'),'6'));
	# Set the value
	if($UserConfig{Events_NotifyPre} eq '0') {
		$TimeSel_Combo->set_active(3);
	} elsif (defined($PreNotificationParser{$UserConfig{Events_NotifyPre}})) {
		$TimeSel_Combo->set_active($PreNotificationParser{$UserConfig{Events_NotifyPre}});
	} else {
		$TimeSel_Combo->set_active($PreNotificationParser{'default'});
	}
	$TimeSel_Combo->show();
	$TimeSel_HBox->pack_start($TimeSel_Combo,0,0,0);
	# Register the changed signal
	$TimeSel_Combo->signal_connect('changed' => sub {
				$UserConfig{Events_NotifyPre} = $PreNotificationParser{$TimeSel_Combo->get_active};
			});
	# TRANSLATORS: See the preferences window, this is preceeded by X minutes or X hour(s).
	my $TimeSel_Label = Gtk2::Label->new(' ' . $i18n->get('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,$i18n->get('Synchronization'));

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

	# Enable/disable evolution checkbox
#	my $EnableDisableEButton = Gtk2::CheckButton->new($i18n->get('Enable GNOME integration'));
#	$Sync_VBox->pack_start($EnableDisableEButton,0,0,0);
#	if(EvoCompat_GetMode())
#	{
#		$EnableDisableEButton->set_active(true);
#	}
#	else
#	{
#		$EnableDisableEButton->set_active(false);
#	}
#	$EnableDisableEButton->signal_connect('toggled' => sub 
#		{
#			if ($EnableDisableEButton->get_active())
#			{
#				if(DPQuestion(
#						$i18n->get("This will enable GNOME integration mode. When enabled you will be able to view Day Planner events in the calendar in the GNOME panel.\n\nIt is not recommended that you enable this if you use Evolution because Day Planner will override certain evolution functions.\n\nAre you sure you want to enable GNOME integration?")
#				))
#				{
#					EvoCompat_Enable();
#					DPInfo("Evolution integration mode has been enabled. You may need to log out and in again for it to take effect.");
#				}
#			}
#			else
#			{
#				EvoCompat_Disable();
#			}
#			if (EvoCompat_GetMode())
#			{
#				$EnableDisableEButton->set_active(true);
#			}
#			else
#			{
#				$EnableDisableEButton->set_active(false);
#			}
#		});
#	$EnableDisableEButton->show();

	# Enable/disable checkbox
	my $EnableDisableCButton = Gtk2::CheckButton->new($i18n->get('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($i18n->get('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(' ' . $i18n->get('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($i18n->get('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($i18n->get('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($i18n->get('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,$i18n->AMPM_From24($Time), $EventSummary]);
			}
		}
	}
	$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();
	
	# Destroy the widget if it already exists
	if ($EventlistWidget)
	{
		$EventlistWidget->destroy();
	}

	# Create the widget and set up signal handlers
	$EventlistWidget = Gtk2::SimpleList->new (
		'UID' => 'hidden',
		# TRANSLATORS: This is followed by the time of an event
		$i18n->get('Time') => 'text',
		$i18n->get('Event') => 'text',
	);
	$EventlistWidget->set_rules_hint(1);
	$EventlistWidget->signal_connect('row_activated' => \&EditEvent);
	$EventlistWin->add($EventlistWidget);
	# Handle making the toolbar edit button sensitive
	$EventlistWidget->signal_connect('focus-in-event' => \&EventListToggle);
	$EventlistWidget->signal_connect('focus-out-event' => \&EventListToggle);
	$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);
					}
				}
			}
		EventListToggle();
		});
	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($i18n->get('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 @MainWindowIcons = GetAppIcons();
	if (@MainWindowIcons)
	{
		$MainWindow->set_icon_list(@MainWindowIcons);
	}

	# 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
		[ '/' . $i18n->get('_Calendar'),						undef,			undef,			0,	'<Branch>'],
		[ '/' . $i18n->get('_Calendar') . '/' . $i18n->get('_Import...'),		'',		\&ImportData,		1,	'<StockItem>',	'gtk-open'],
		[ '/' . $i18n->get('_Calendar') . '/' . $i18n->get('_Export...'),		undef,		\&ExportData,		1,	'<StockItem>',	'gtk-save-as'],
		[ "/" . $i18n->get("_Calendar") . '/sep',					undef,			undef,			2,	'<Separator>'],
		[ "/" . $i18n->get("_Calendar") . "/$QuitStock",		'<control>Q',		\&QuitSub,		3,	'<StockItem>',	'gtk-quit'],
		# Edit menu
		[ "/$EditStock", undef,			undef,			0,	'<Branch>'],
		[ "/$EditStock/" . $i18n->get('_Add an Event...'),		'<control>A',		\&AddEvent,		1,	'<StockItem>',	'gtk-add' ],
		[ "/$EditStock/" . $i18n->get('_Edit This Event...'),	'<control>E',		\&EditEvent,		2,	'<StockItem>',	'gtk-edit' ],
		[ "/$EditStock/" . $i18n->get('_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/" . $i18n->get('_Report a bug'), undef, \&ReportBug, 0, '<StockItem>', 'gtk-dialog-warning'],
		[ "/$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/" . $i18n->get('_Edit This Event...');
	$Get =~ s/_//g;
	$MenuEditEntry = $Menu_ItemFactory->get_widget($Get);

	$Get = "/$EditStock/" . $i18n->get('_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);
			DrawEventlist();
		});

	# UPCOMING EVENTS
	# Create the scrolled window
	my $UpcomingEventsWindow = Gtk2::ScrolledWindow->new;
	$UpcomingEventsWindow->set_policy('automatic', 'automatic');
	$UpcomingEventsWindow->show();
	# Create the TextView and TextBuffer objects
	$UpcomingEventsWidget = Gtk2::TextView->new();
	$UpcomingEventsBuffer = Gtk2::TextBuffer->new();
	$UpcomingEventsWidget->set_buffer($UpcomingEventsBuffer);
	$UpcomingEventsWidget->set_editable(0);
	$UpcomingEventsWidget->set_wrap_mode('word');
	$UpcomingEventsWidget->show();
	$UpcomingEventsWindow->add($UpcomingEventsWidget);
	# Pack it onto the main window
	$RightHandVBox->pack_end($UpcomingEventsWindow,1,1,0);
	
	# ==================================================================
	# 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,$i18n->get('Delete the selected event'),'');
	$Tooltips->set_tip($ToolbarDeleteButton,$i18n->get('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,$i18n->get('Edit the selected event'),'');
	$Tooltips->set_tip($ToolbarEditButton,$i18n->get('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,$i18n->get('Add a new event'),'');
	$Tooltips->set_tip($AddButton,$i18n->get('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 = shift;
	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);
	}

	# If we're not in GUI mode, load the configuration file.
	# We load it later if in GUI mode in order to give the illusion of the
	# app starting faster
	if($NoGui) {
		LoadConfig();
	}
	# Load the internal state file
	LoadStateFile();
	# Set up holidays
	HolidaySetup();
	# Load the calendar
	LoadCalendar();
	return($FirstStartup);
}

# Purpose: Initialize the core program
# Usage: Gtk2->init_add(\&InitDPCore);
sub InitDPCore {
	DP_InitI18n();
	if(not defined($ENV{DP_DISABLE_EXCEPTIONSHANDLER}) or $ENV{DP_DISABLE_EXCEPTIONSHANDLER}) {
		my $Exception =  Glib->install_exception_handler(sub {
			my $Exception = shift;
			my $Chomped = $Exception;
			chomp($Chomped);
			# This is the DP perl exception handler
			DPIntWarn("An exception occurred: $Chomped");
			# Display an error
			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$Exception");
			# Make the main window sensitive - this so that the program is at least
			# partially usable after the exception occurred.
			$MainWindow->set_sensitive(1);
			1;
		});
	}
	
	# Flush filehandles faster if requested
	if(defined($ENV{DP_FH_FORCEFLUSH}) and $ENV{DP_FH_FORCEFLUSH}) {
		$| = 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();
	# Create the socket
	$IPC_Socket = DP::GeneralHelpers::IPC->new_server("$SaveToDir/ipcsocket",\&IPC_Handler);
	if(not $IPC_Socket) {
		my $Client = DP::GeneralHelpers::IPC->new_client("$SaveToDir/ipcsocket",sub { return });
		if(@ARGV) {
			foreach(@ARGV) {
				$Client->client_send("IMPORT_DATA $_");
			}
			exit(0);
		}
		AlreadyRunningDP($Client);
		$IPC_Socket = DP::GeneralHelpers::IPC->new_server("$SaveToDir/ipcsocket",\&IPC_Handler);
	}
	# Draw the main window
	DrawMainWindow();
	$MainWindow->show() if not $FirstStartup;
	Gtk2->main_iteration while Gtk2->events_pending;
	# Load the config
	LoadConfig();
	# Import data as told on the commandline
	if(@ARGV) {
		foreach(@ARGV) {
			IPC_Handler("IMPORT_DATA $_");
		}
	}
	# Connect to a DPS server and sync the local data with it if it's enabled
	DPS_Perform('SYNC');
	# Initialize the daemon
	DaemonInit() or die();
	# Finally, initialize HTTP subscriptions if needed
	LoadHTTPSubscriptions();
	# Add autostart if needed
	unless(defined($InternalConfig{AddedAutostart}) and $InternalConfig{AddedAutostart} eq "1") {
		DP_AddAutostart();
		$InternalConfig{AddedAutostart} = 1;
	}
	# Set the redraw timer and populate the upcoming events widget
	PopulateUpcomingEvents();
	Set_DayChangeTimer();
}

# Get commandline options
GetOptions (
	'help|h' => sub {
		print "Day Planner version $Version\n\n";
		PrintHelp('-h','--help','Display this help screen and exit');
		PrintHelp('-v','--version', 'Display version information and exit');
		PrintHelp('-t','--test','Use a seperate debug/ configuration directory');
		PrintHelp('', '--confdir', 'Use the directory supplied as configuration directory');
		PrintHelp('-s', '--shutdaemon', 'Shutdown the daemon when Day Planner is closed');
		PrintHelp('', '--exportical', 'Export the Day Planner data in the iCalendar format');
		PrintHelp('', '', 'to the filename supplied');
		PrintHelp('', '--exporthtml', 'Export the Day Planner data in the HTML format to the');
		PrintHelp('', '', 'directory supplied');
		PrintHelp('', '--importical', 'Import data from the iCalendar file specified');
		PrintHelp('','--debuginfo', 'Display information useful for debugging and exit');
		exit(0);
	},
	'version|v' => sub {
		print "Day Planner version $Version \"$VersionName\"\n";
		print "RCS revision: $RCSRev\n";
		exit(0);
	},
	'debuginfo' => sub {
		$| = 1;
		$SaveToDir = DetectConfDir();
		print "Day Planner version $Version - $RCSRev\n";
		print "Using config dir: $SaveToDir\n";
		printf "Perl version %vd\n", $^V;
		print 'Gtk2 version ', join ('.', Gtk2->GET_VERSION_INFO),"\n";
		print 'OS: ', GetDistVer(), "\n";
		my $Prev;
		# Display module information (also includes daemon and notifier deps)
		my @AvailModules;
		my @UnavailModules;
		print "Available modules:";
		foreach my $Module (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 DP::GeneralHelpers IO::Socket::SSL IPC::Open2 Net::DBus Sys::Hostname/) {
			if(eval("use $Module; 1")) {
				if($Prev) {
					print ',';
				} else {
					$Prev = 1;
				}
				my $ModVer = eval("if(defined(\$$Module\:\:VERSION)) { return \$$Module\:\:VERSION}else { return undef;}");
				if($ModVer and $ModVer =~ /\S/ and length($ModVer) > 1 and $ModVer =~ /\d/) {
					$ModVer =~ s/_//g;	# Some version numbers use the _ seperator inside numbers
								# printf() doesn't like it so we remove it.
					print " $Module $ModVer";
				} else {
					print " $Module";
				}
			} else {
				push(@UnavailModules, $Module);
			}
		}
		print "\nUnavailable modules:";
		print " $_" foreach(@UnavailModules);
		print ' (none)' unless(@UnavailModules);
		print "\n";
		print '@INC:';
		$Prev = false;
		foreach my $i (@INC) {
			if ($Prev)
			{
				print ',';
			}
			else
			{
				$Prev = true;
			}
			print " '".$i."'";
		}
		print "\n";
		$Prev = undef;
		print 'I18n: ';
		foreach (sort keys(%ENV))
		{
			if (/^(LC_|LANG)/)
			{
				print " || " if $Prev;
				$Prev = true;
				print "$_=$ENV{$_}";
			}
		}
		if ($Prev)
		{
			print "\n";
		}
		else
		{
			print "(none detected)\n";
		}
		DP_InitI18n();
		print 'I18n workaround: '. ($i18n->{workaround} ? 'on' : 'off');
		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,
	# Commandline supprot for experimental evocompat
	'evocompat-enable' => sub 
	{
		DPIntWarn("EvoCompat is experimental, use at your own risk");
		DP_InitI18n();
		$SaveToDir = DetectConfDir();
		EvoCompat_Enable();
		exit(0);
	},
	'evocompat-disable' => sub 
	{
		DPIntWarn("EvoCompat is experimental, use at your own risk");
		DP_InitI18n();
		$SaveToDir = DetectConfDir();
		EvoCompat_Disable();
		exit(0);
	},
	'evocompat-status' => sub 
	{
		DPIntWarn("EvoCompat is experimental, use at your own risk");
		DP_InitI18n();
		$SaveToDir = DetectConfDir();
		print EvoCompat_GetMode() ? "EvoCompat enabled" : "EvoCompat Disabled";print "\n";
		exit(0);
	},
	# Compatibility for evolution-commandline
	'component=s' => sub {
		if ( not $_[1] eq 'calendar')
		{
			DPIntWarn("Evolution compat: --component was not calendar - Day Planner does not support '$_[1]'");
		}
	},
) or die "Run $0 --help for more information\n";

my $Gtk2_Initialized = 0;

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