#!/usr/bin/perl
# Day Planner
# A graphical Day Planner written in perl that uses Gtk2
# Copyright (C) Eskild Hustvedt 2006, 2007, 2008, 2009, 2012
#
# 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 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/DP-iCalendar/lib/";
use lib "$FindBin::RealBin/modules/dayplanner/";
# External deps as fetched by the makefile
use lib "$FindBin::RealBin/modules/external/";
# Day Planner-specific libs
use Date::HolidayParser::iCalendar 0.4;	# Parsing of .holiday files
use DP::CoreModules::Plugin;
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 PrefixZero);
use DP::GeneralHelpers::IPC;
use DP::GeneralHelpers::I18N;

# Scalars
our $Version = '0.11';
my $VersionName = '2012';
my $SaveToDir;						# The configuration and eventlist directory (set later)
my $HolidayFile;					# The file to load holiday definitions from (set later)
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 $iCalendar;						# The DP::iCalendar::Manager object.
my $IPC_Socket;
my $plugin;

# Arrays
# FIXME: Unused
#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

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

my $HolidayParser;	# The Date::HolidayParser object
my $Gtk2Init;		# Info about if gtk2 is initialized or not
my $ShutdownDaemon;	# If we should shut down the daemon on exit
my $NoDaemon;		# True if we should dsable all use of the daemon. Useful on embedded systems
					# as the daemon keeps its own copy of the data and config files in memory.

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

# 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
{
	Assert(!$i18n);
	$i18n = DP::GeneralHelpers::I18N->new('dayplanner');
	Assert($i18n);
}

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

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

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

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

# Purpose: Initialize and connect to the daemon
# Usage: DaemonInit();
sub DaemonInit
{
	return if $NoDaemon; # Don't do anything if the daemon is disabled
	$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
{
	return if $NoDaemon; # Don't do anything if the daemon is disabled
	if (-e "$SaveToDir/$DaemonSocketName")
	{
		my $daemonConnErr;
		($DaemonSocket,$daemonConnErr) = DP::GeneralHelpers::IPC->new_client("$SaveToDir/$DaemonSocketName",sub { return });
		# TODO: Check daemon version.
		#my $DaemonVersion = $DaemonSocket->client_send_blocking('VERSION');
		return($DaemonSocket);
	}
	return(false);
}

# Purpose: Start the Day Planner daemon
# Usage: StartDaemon();
sub StartDaemon
{
	return if $NoDaemon; # Don't do anything if the daemon is disabled
	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
			if (not 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 && !$NoDaemon)
	{
		$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
{
	return if $NoDaemon; # Don't do anything if the daemon is disabled
	if (not $DaemonSocket)
	{
		return(undef);
	}
	Assert(scalar(@_) == 1);
	$DaemonSocket->client_send("$$ $_[0]");
}

# Purpose: Create the plugin object and load plugins
# Usage: init_plugins();
sub init_plugins
{
	my $paths = [ $SaveToDir.'/plugins/',$FindBin::RealBin.'/plugins/'];
	$plugin = DP::CoreModules::Plugin->new(\%InternalConfig);
	$plugin->register_signals(qw(SHUTDOWN INIT SAVEDATA CREATE_MENUITEMS BUILD_TOOLBAR IPC_IN));
	$plugin->set_var('calendar',$iCalendar);
	$plugin->set_var('state',\%InternalConfig);
	$plugin->set_var('config',\%UserConfig);
	$plugin->set_var('Gtk2Init',$Gtk2Init);
	$plugin->set_var('i18n',$i18n);
	$plugin->set_var('version',$Version);
	$plugin->set_var('confdir',$SaveToDir);
	$plugin->set_var('pluginPaths',$paths);
	$plugin->set_searchpath($paths);
	if (defined $ENV{DP_DISABLE_PLUGINS} and $ENV{DP_DISABLE_PLUGINS} eq '1')
	{
		DPIntWarn('Plugins disabled by the DP_DISABLE_PLUGINS environment variable');
		return;
	}
	foreach my $pName (split(/\s+/,$InternalConfig{plugins_enabled}))
	{
		$plugin->load_plugin_if_missing($pName,$paths);
	}
	if ($ENV{DP_LOAD_PLUGINS})
	{
		foreach my $pName (split(/\s+/,$ENV{DP_LOAD_PLUGINS}))
		{
			$plugin->load_plugin_if_missing($pName,$paths);
		}
	}
}

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

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

	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-axis 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',
		plugins_enabled => "A space-separated list of plugins to load on startup\n# WARNING: Modify at your own risk, plugins that are present here are\n# not checked for dependencies, you should use the plugins GUI\n# available from within Day Planner instead, to ensure dependencies are resolved",
		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
{
	$plugin->signal_emit('SAVEDATA');
	$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/calendar.ics")
	{
		# Load an already existing calendar
		$MainCalendar = DP::iCalendar->new("$SaveToDir/calendar.ics");
	}
	else
	{
		# Create a new calendar
		$MainCalendar = DP::iCalendar->newfile("$SaveToDir/calendar.ics");
		# 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::iCalendar->new("$SaveToDir/holidays");
		# 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");
#  Description will be reformatted to fit within a normal terminal
sub PrintHelp
{
	# The short option
	my $short = shift,
	# The long option
	my $long = shift;
	# The description
	my $desc = shift;
	# The generated description that will be printed in the end
	my $GeneratedDesc;
	# The current line of the description
	my $currdesc = '';
	# The maximum length any line can be
	my $maxlen = 80;
	# The length the options take up
	my $optionlen = 20;
	# Check if the short/long are LONGER than optionlen, if so, we need
	# to do some additional magic to take up only $maxlen.
	# The +1 here is because we always add a space between them, no matter what
	if ((length($short) + length($long) + 1) > $optionlen)
	{
		$optionlen = length($short) + length($long) + 1;
	}
	# Split the description into lines
	foreach my $part (split(/ /,$desc))
	{
		if (defined $GeneratedDesc)
		{
			if ((length($currdesc) + length($part) + 1 + 20) > $maxlen)
			{
				$GeneratedDesc .= "\n";
				$currdesc = '';
			}
			else
			{
				$currdesc .= ' ';
				$GeneratedDesc .= ' ';
			}
		}
		$currdesc .= $part;
		$GeneratedDesc .= $part;
	}
	# Something went wrong
	die("Option mismatch") if not $GeneratedDesc;
	# Print it all
	foreach my $description (split(/\n/,$GeneratedDesc))
	{
		printf "%-4s %-15s %s\n", $short,$long,$description;
		# Set short and long to '' to ensure we don't print the options twice
		$short = '';$long = '';
	}
	# Succeed
	return true;
}

# Purpose: Get debugging info
# Usage: GetDebugInfo(SCALAR?);
# 	If SCALAR is true then returns a scalar, if not prints to STDOUT.
sub GetDebugInfo
{
	my $scalar = shift;

	my $target;
	if ($scalar)
	{
		$scalar = '';
		open($target, '>',\$scalar);
	}
	else
	{
		open($target, '>&', \*STDOUT);
		my $orig_fh = select($target);
		$| = 1;
		select($orig_fh);
	}
	$SaveToDir = DetectConfDir();

	my %digests;
	{
		runtime_use('Digest::MD5');
		my $fail = 0;
		foreach my $cFile (qw(dayplanner dayplanner-daemon dayplanner-notifier))
		{
			my $oFile = $cFile;
			$oFile =~ s/^dayplanner-//;
			$digests{$oFile} = '(unknown)';
			if (not -e $FindBin::RealBin.'/'.$cFile)
			{
				$fail = 1;
				warn("ERROR: Failed to locate: $FindBin::RealBin/$cFile\n");
				next;
			}
			my $f;
			open($f,'<',$FindBin::RealBin.'/'.$cFile) or $f = undef;
			if (not $f)
			{
				$fail = 1;
				warn("ERROR: Failed to open $FindBin::RealBin/$cFile for reading: $!\n");
				next;
			}
			my $md5 = Digest::MD5->new();
			binmode($f);
			$md5->addfile($f);
			$digests{$oFile} = $md5->hexdigest;
			close($f);
		}
	}

	print {$target} "Day Planner version $Version";
	if ($VersionName eq 'GIT')
	{
		print {$target} " (git snapshot)\n";
	}
	else
	{
		print {$target} " ($VersionName)\n";
	}
	printf {$target} "%-15s: %s\n", "MD5" ,$digests{dayplanner};
	printf {$target} "%-15s: %s\n", "MD5 (daemon)", $digests{daemon};
	printf {$target} "%-15s: %s\n", "MD5 (notifier)", $digests{notifier};
	print {$target} "Using config dir: $SaveToDir\n";
	printf {$target} "Perl version %vd\n", $^V;
	print {$target} 'Gtk2 version ', join ('.', Gtk2->GET_VERSION_INFO),"\n";
	print {$target} 'OS: ', GetDistVer(), "\n";
	my $Prev;
	# Display module information (also includes daemon and notifier deps)
	my @AvailModules;
	my @UnavailModules;
	print {$target} "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::iCalendar IO::Select DP::iCalendar DP::GeneralHelpers IO::Socket::SSL IPC::Open2 Net::DBus Sys::Hostname/)
	{
		if (eval("use $Module; 1"))
		{
			if ($Prev)
			{
				print {$target} ',';
			}
			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 {$target} " $Module $ModVer";
			}
			else
			{
				print {$target} " $Module";
			}
		}
		else
		{
			push(@UnavailModules, $Module);
		}
	}
	print {$target} "\nUnavailable modules:";
	print {$target} " $_" foreach(@UnavailModules);
	print {$target} ' (none)' if (not @UnavailModules);
	print {$target} "\n";
	print {$target} '@INC:';
	$Prev = false;
	foreach my $i (@INC)
	{
		if ($Prev)
		{
			print {$target} ',';
		}
		else
		{
			$Prev = true;
		}
		print {$target} " '".$i."'";
	}
	print {$target} "\n";
	$Prev = undef;
	print {$target} 'I18n: ';
	foreach (sort keys(%ENV))
	{
		if (/^(LC_|LANG)/)
		{
			print {$target} " || " if $Prev;
			$Prev = true;
			print {$target} "$_=$ENV{$_}";
		}
	}
	if ($Prev)
	{
		print {$target} "\n";
	}
	else
	{
		print {$target} "(none detected)\n";
	}
	if (not $i18n)
	{
		DP_InitI18n();
	}
	print {$target} 'I18n workaround: '. ($i18n->{workaround} ? 'on' : 'off');
	print {$target} "\n";
	if (not keys %InternalConfig)
	{
		LoadStateFile();
	}
	print {$target} 'Plugins enabled: ';
	foreach my $pName (split(/\s+/,$InternalConfig{plugins_enabled}))
	{
		print {$target} $pName.' ';
	}
	if ($ENV{DP_LOAD_PLUGINS})
	{
		print {$target} '- via DP_LOAD_PLUGINS: ';
		foreach my $pName (split(/\s+/,$ENV{DP_LOAD_PLUGINS}))
		{
			print {$target} $pName.' ';
		}
	}
	print {$target} "\n";
	return $scalar;
}

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

# Purpose: Die with useful information if an Assertion fails
# Usage: Assert(TRUE/FALSE EXPR, REASON);
sub Assert
{
	my $expr = shift;
	return true if $expr;
	my ($package, $filename, $line, $subroutine, $hasargs, $wantarray, $evaltext, $is_require, $hints, $bitmask) = caller(1);
	my ($s2_package, $s2_filename, $s2_line, $s2_subroutine, $s2_hasargs, $s2_wantarray, $s2_evaltext, $s2_is_require, $s2_hints, $s2_bitmask) = caller(0);
	my $msg = "Assertion failure at $s2_filename:$s2_line in $subroutine originating from call at $filename:$line";
	if (defined($_[0]))
	{
		$msg .= ': '.$_[0]."\n";
	}
	else
	{
		$msg .= "\n";
	}
	# If this is the GIT version or DP_FATAL_ASSERT is set, then the error is fatal.
	if ($VersionName eq 'GIT' or (defined($ENV{DP_FATAL_ASSERT}) and $ENV{DP_FATAL_ASSERT} eq 1))
	{
		if (defined $SaveToDir && -d $SaveToDir && defined $iCalendar)
		{
			(my $loctime = scalar localtime) =~ s/\s+/_/g;
			my $bakFile = $SaveToDir.'/assertionFailureBackup.'.time().'.ics';
			$iCalendar->write($bakFile);
			die($msg.'Calendar backup written to: '.$bakFile."\n");
		}
		die($msg);
	}
	# If not, then we output it and happily continue processing
	else
	{
		# TODO: This should probably get displayed in a GUI dialog aswell,
		# if possible.
		DPIntWarn($msg);
		# Attempt to make the main window usable again, in case
		# this screws things up.
		$MainWindow->set_sensitive(1);
	}
	return true;
}

# Purpose: Display an error about an already running Day Planner session
# Usage: AlreadyRunningDP(IPC_CLIENT);
sub AlreadyRunningDP
{
	my $ClientConnection = shift;
	Assert(defined $ClientConnection);

	my $display = $ClientConnection->client_send_blocking('GETDISPLAY');
	if (defined $display and length $display and defined $ENV{DISPLAY})
	{
		if ($display eq $ENV{DISPLAY})
		{
			my $reply = $ClientConnection->client_send_blocking('SHOWHIDE');
			if ($reply eq 'OK')
			{
				exit(0);
			}
		}
	}

	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;
			my $pid = $ClientConnection->client_send_blocking('GETPID');
			$ClientConnection->client_send('QUIT');
			# Use this for nanosleep so that we don't sleep any longer than
			# we absolutely have to.
			runtime_use('Time::HiRes');
			# Wait max 4 seconds, and only 1 second if we don't have a PID
			my $waitsec = 16;
			if (not defined $pid or $pid =~ /\D/)
			{
				$waitsec = 4;
			}
			for(my $l = 0; $l <= $waitsec; $l++)
			{
				if (defined $pid and $pid =~ /^\d+$/)
				{
					if (kill(0,$pid) == 0)
					{
						last;
					}
				}
				Time::HiRes::nanosleep(250);
				sleep(1);
			}
			$ClientConnection->destroy();
			# 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);

	$plugin->delete_var('IPC_REPLY');
	$plugin->set_tempvar('IPC_REQUEST',$Message);
	my $aborted = $plugin->signal_emit('IPC_IN');
	if ($aborted)
	{
		if (my $ret = $plugin->get_var('IPC_REPLY'))
		{
			$plugin->delete_var('IPC_REPLY');
			return $ret;
		}
		else
		{
			DPIntWarn('A plugin aborted the IPC_Handler but did not ->set_var("IPC_REPLY",...), don\'t know what to reply, so ignoring the abort request');
		}
	}

	if ($Message =~ s/^IMPORT_DATA\s+(.+)$/$1/)
	{
		if (-e $Message && -r $Message)
		{
			$iCalendar->addfile($Message);
			print "Imported $Message\n";
			UpdatedData();
		}
		else
		{
			print "\"$Message\": did not exist\n";
		}
	}
	# This is used to check if we're alive, the syntax is:
	# ALIVE DISPLAY
	# Where DISPLAY is the X display ($ENV{DISPLAY}) that the caller is running
	# on. The usual reply is ALIVEANDWELL, but other replies can be returned by 
	# plugins that abort us.
	elsif ($Message =~ s/^ALIVE\s+//)
	{
		return('ALIVEANDWELL');
	}
	elsif ($Message =~ /^QUIT/)
	{
		# This is the same as ctrl+c
		DP_SigHandler('IPC_QUIT_REQUEST');
	} 
	elsif ($Message =~ /^GETPID/)
	{
		return($$);
	}
	elsif ($Message =~ /^GETDISPLAY/)
	{
		my $display = $ENV{DISPLAY} ? $ENV{DISPLAY} : '';
		return ($display);
	}
	elsif ($Message =~ /^SHOWHIDE/)
	{
		if ($MainWindow)
		{
			$MainWindow->hide();
			$MainWindow->show();
			$MainWindow->present();
			return 'OK';
		}
		else
		{
			return 'FAILURE';
		}
	}
	# This isn't used anywhere yet, but is added in case it is needed
	# in future versions that needs to be able to talk to this one.
	elsif ($Message =~ /^GETVERSION/)
	{
		return($Version);
	}
	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 = 10;
	# 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/calendar.ics")
	{
		# 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);
	my $hadDPS = 0;
	# Upgrade DPS values (0.9 and older) to plugin ones
	foreach my $val(qw(DPS_enable DPS_host DPS_pass DPS_port DPS_user DPS_LastMD5))
	{
		if ($UserConfig{$val})
		{
			if (not $InternalConfig{'ServicesSync_'.$val})
			{
				$hadDPS = 1;
				$InternalConfig{'ServicesSync_'.$val} = $UserConfig{$val};
			}
			delete($UserConfig{$val});
		}
	}
	if ($hadDPS)
	{
		$InternalConfig{plugins_enabled} .= ' ServicesSync';
	}
	if (not $InternalConfig{plugins_enabled} =~ /PluginManager/)
	{
		$InternalConfig{plugins_enabled} .= ' PluginManager';
	}
	WriteConfig();
	ProgressMade(10, $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
	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)
		{
			if (not /$DaemonName/)
			{
				chomp;
				print $FLUXSTART "$_\n";
			}
		}
		close($FLUXSTART);
	}
	return(1);
}

# Purpose: Add the daemon to autostart for the various DMs/WMs
# Usage: DP_AddAutostart();
sub DP_AddAutostart
{
	# Set the settings var
	$InternalConfig{AutostartOn} = 1;

	my $DaemonExec;
	foreach(split(/:/, sprintf('%s:%s', $FindBin::RealBin, $ENV{PATH} )))
	{
		if (-x "$_/$DaemonName")
		{
			$DaemonExec = "$_/$DaemonName";
			last;
		}
	}
	Assert(defined $DaemonExec,'Daemon detection failed');

	# Freedesktop spec, most desktops honour this now
	if (-d "$ENV{HOME}/.config/")
	{
		mkdir("$ENV{HOME}/.config/autostart/") if not -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);
	}

	# KDE
	if (-d "$ENV{HOME}/.kde/")
	{
		mkdir("$ENV{HOME}/.kde/Autostart/") if not -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');
		}
	}

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

# =============================================================================
# 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');
		if (not -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)
		{
			if (not -w $Target)
			{
				die("I don't have write permission to $Target\n");
			}
			if (not -d $Target)
			{
				die("$Target: is not a directory\n");
			}
		}
		if ($Type =~ /html/)
		{
			runtime_use('DP::iCalendar::WebExp') or die('Failed to load the requried web export module, your Day Planner install appears to be corrupt');
			my $html = DP::iCalendar::WebExp->new();
			$html->set_dpi($iCalendar);
			$html->writehtml($Target);
		}
		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") if (not -e $Source);
	die("$Source: is not readable by me\n") if (not -r $Source);
	die("$Source: is a directory\n") if (-d $Source);
	# Initialize the daemon (we need it)
	Assert(DaemonInit());
	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);
}

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

# Purpose: Purge the current $PlanConvertHash-> buffer and put it into %CalendarContents
# Usage: PlanConvert_PurgeBuffer(\%PlanConvertHash);
sub PlanConvert_PurgeBuffer
{
	my $PlanConvertHash = $_[0];
	Assert(defined $PlanConvertHash);
	my $ErrorMessage;
	if (defined($PlanConvertHash->{Day}))
	{
		Assert($PlanConvertHash->{Type} =~ /^(normal|bday)$/);
		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);
		}
		$PlanConvertHash->{Type} = 'normal';
	}
}

# Purpose: Convert the file supplied
# Usage: PlanConvert_PurgeBuffer(/path/to/file,\%PlanConvertHash, $ProgressBar);
sub PlanConvert_ProcessFile
{
	Assert(scalar(@_) > 1);
	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 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->set_type_hint('dialog');
	$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_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\nIf you wish to import calendar data from other applications, select the application(s) now and press \"Import\" to continue, otherwise press \"Don't import\".") : $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');
	$ImportLabel->set_line_wrap(1);
	$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
	# TRANSLATORS: Button in the "import from program" window
	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');
	# TRANSLATORS: Button in the "import from program" window
	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;
		});
	$ImportWindow->signal_connect('destroy' => sub {
			$ImportWindow->destroy();
			$MainWindow->show() if $FirstTime;
			$MainWindow->set_sensitive(1) if $MainWindow;
		});
	$ImportWindow->signal_connect('delete-event' => 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
	# FIXME
	my $ImportWindow = Gtk2::FileChooserDialog->new($i18n->get('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')
		{
			Assert($ActiveIndex == 0 || $ActiveIndex == 1);
			#my $OnlyBirthdays = $BirthdaysOnlyCheckbox->get_active();
			if ($ActiveIndex == 0)
			{
				if (PromptOverwrite("$Filename.ics"))
				{
					$iCalendar->write("$Filename.ics");
					last;
				}
			}
			elsif ($ActiveIndex == 1)
			{
				if (PromptOverwrite($Filename, 1))
				{
					runtime_use('DP::iCalendar::WebExp') or die();
					my $html = DP::iCalendar::WebExp->new();
					$html->set_dpi($iCalendar);
					$html->writehtml($Filename);
					last;
				}
			}
		}
		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/*"))
				{
					if (not $_ =~ /^\./)
					{
						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);
		if (not -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: Display debugging info in a window
# Usage: DisplayDebugWindow();
sub DisplayDebugWindow
{
	my $info = GetDebugInfo(true);
	DPInfo($info);
}

# Purpose: Flush the UI buffer
# Usage: GTK_Flush();
sub GTK_Flush
{
	Gtk2->main_iteration while Gtk2->events_pending;
	Gtk2->main_iteration while Gtk2->events_pending;
	Gtk2->main_iteration while Gtk2->events_pending;
}

# 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) = @_;
	Assert(scalar(@_) == 3);
	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();
	GTK_Flush();
	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();
		GTK_Flush();
	}
	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);
	}
	GTK_Flush();
	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);
		GTK_Flush();
	}
}

# Purpose: Populate the upcoming events widget
# Usage: PopulateUpcomingEvents();
sub PopulateUpcomingEvents
{
	my $NewUpcoming = GetUpcomingEventsString($iCalendar);
	Assert(defined $NewUpcoming);
	# 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
	Assert(defined $Selected);
	my $Type = GetEventListType();

	my @StandardTypes = ( 'normal', 'allday','bday' );
	Assert(grep($Type, @StandardTypes) || $Type eq 'holiday');

	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);
		GTK_Flush();
		$iCalendar->delete($EventUID);

		UpdatedData(0,1);
	}
	elsif ($Type eq 'holiday')
	{
		DPInfo(sprintf($i18n->get("This is a predefined \"holiday\" event. This event cannot be edited directly from within Day Planner. In order to change these events use a text editor and edit the file %s."), $HolidayFile));
	}
}

# 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)
	if (not 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
{
	my $type = shift;
	$MainWindow->set_sensitive(0);
	$plugin->signal_emit('SHUTDOWN');
	my $SaveData = SaveMainData();
	if ($SaveData eq 'SAVE_FAILED')
	{
		if (not DPQuestion($i18n->get('Some files could not be saved correctly. Quit anyway?')))
		{
			$MainWindow->show();
			return(1);
		}
	}
	WriteStateFile($SaveToDir, 'state.conf');

	Gtk2->main_quit;
	CloseIPC();
	if (defined $type and $type eq 'RESTART')
	{
		exec($0,'--confdir',$SaveToDir);
	}
	else
	{
		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
{
	Assert(defined $CalendarWidget);
	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
{
	Assert(scalar(@_) >= 4);
	my($Entry,$Year,$Month,$Day) = @_;

	$Month = PrefixZero($Month);
	$Day = PrefixZero($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/undef - 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
{
	Assert(scalar(@_) > 2);
	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;

	Assert($Mode eq 'MULTI' || $Mode eq 'SINGLE');

	# Create the window and stuff inside the window
	my $Window = Gtk2::Window->new();
	$Window->set_deletable(0) if $Window->can('set_deletable');
	$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
{
	my ($HourSpinner, $MinuteSpinner, $AMPM) = @_;	
	Assert(scalar(@_) > 1);
	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;
	Assert(defined $Hash);
	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;
	Assert(defined $Entry);
	if (not $Entry->is_sensitive)
	{
		return([]);
	}
	my $EntryArray = ParseEntryField($Entry);
	my @ReturnArray;
	foreach my $Date (@{$EntryArray})
	{
		my($Year,$Month,$Day) = ParseDateString($Date);
		$Month = PrefixZero($Month);
		$Day = PrefixZero($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;
	Assert(defined $Entry);
	if (not $Entry->is_sensitive)
	{
		return(undef);
	}
	my $EntryArray = ParseEntryField($Entry);
	my @ReturnArray;
	foreach my $Date (@{$EntryArray})
	{
		my($Year,$Month,$Day) = ParseDateString($Date);
		$Month = PrefixZero($Month);
		$Day = PrefixZero($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;
	Assert(defined $Time);
	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) = @_;
	Assert(scalar(@_) < 4);

	# 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)
	Assert(defined $Selected);

	$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();

	Assert($Type =~ /^(normal|bday|allday|holiday)$/);

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

	$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) = @_;
	Assert(scalar(@_) == 4);

	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*
	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
	if (not $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) = @_;
	Assert(scalar(@_) > 2);

	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
	if (not $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) = @_;
	Assert(scalar(@_) == 3);
	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" .
		'Kalle Persson (the Day Planner icon)'
	);
	$AboutDialog->set_copyright('Copyright (C) Eskild Hustvedt 2006-2012');
	$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) or $Gtk2::VERSION >= 1.183)
	{
		$AboutDialog->set_program_name($AppName);
	}
	else
	{
		$AboutDialog->set_name($AppName);
	}
	$AboutDialog->set_version($Version);
	# Add revision when we're the GIT version
	if ($VersionName eq 'GIT')
	{
		$AboutDialog->set_version($Version.' GIT snapshot');
	}
	# 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(5);
	$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);

	# ==================================================================
	# ==================================================================
	# REMINDERS TAB
	# ==================================================================
	# ==================================================================

	# 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();
	$MainVBox->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
			{
				if (not 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);
	$MainVBox->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();
	$MainVBox->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();
	$MainVBox->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');
	}

	# ==================================================================
	# FINALIZE WINDOW
	# ==================================================================
	my $ClosePerform = sub
	{
		WriteConfig();
		$PreferencesWindow->hide();
		$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
{
	Assert(scalar(@_) == 0);
	@{$EventlistWidget->{data}} = ();
	if (shift)
	{
		DPIntWarn('PopulateEventList() called directly - this is deprecated. Call DrawEventlist() instead to ensure it is redrawn properly. Calling it for you now.');
		return DrawEventlist();
	}
	my $Year = $CalendarWidget->year;
	my $Month = $CalendarWidget->month;$Month++;
	my $Day = $CalendarWidget->selected_day;

	# New eventlist so make the toolbar edit button and edit/delete menu entries insensitive
	$ToolbarEditButton->set_sensitive(0);
	$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();
	EventListToggle();
}

# Purpose: Draw the eventlist on the currently selected date in the calendar
# Usage: DrawEventlist();
sub DrawEventlist
{
	Assert(scalar(@_) == 0);
	# 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
{
	Assert(scalar(@_) == 0);
	# ==================================================================
	# 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
	# NOTE: Some items where their location is important aren't added until later, after the signal
	my @MenuItems = (
		# Calendar menu
		[ '/' . $i18n->get('_Calendar'),						undef,			undef,			0,	'<Branch>'],
		[ '/' . $i18n->get('_Calendar') . '/' . 
		# TRANSLATORS: Menu selection in the Calendar menu
		$i18n->get('_Import file...'),		'',		\&ImportDataFromFile,		1,	'<StockItem>',	'gtk-open'],
		[ '/' . $i18n->get('_Calendar') . '/' . 
		# TRANSLATORS: Menu selection in the Calendar menu
		$i18n->get('Import from _program...'),		'',		\&ImportDataFromProgram,		1,	'<StockItem>',	'gtk-revert-to-saved'],
		[ '/' . $i18n->get('_Calendar') . '/' . 
		# TRANSLATORS: Menu selection in the Calendar menu. Exports calendar data.
		$i18n->get('_Export...'),		undef,		\&ExportData,		1,	'<StockItem>',	'gtk-save-as'],
		[ "/" . $i18n->get("_Calendar") . '/sep',					undef,			undef,			2,	'<Separator>'],
		# 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>'],
		# Help menu
		[ "/$HelpStock",						undef,			undef,			0,	'<Branch>' ],
		[ "/$HelpStock/" . $i18n->get('_Report a bug'), undef, \&ReportBug, 0, '<StockItem>', 'gtk-dialog-warning'],
	);
	# 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);

	# Let plugins edit it
	$plugin->set_tempvar('MenuItems', \@MenuItems);
	$plugin->set_tempvar('HelpName', $HelpStock);
	$plugin->set_tempvar('EditName', $EditStock);
	$plugin->signal_emit('CREATE_MENUITEMS');

	# The items that should be the last ones in the menu
	my @AdditionalItems = (
		[ "/" . $i18n->get("_Calendar") . "/$QuitStock",		'<control>Q',		\&QuitSub,		3,	'<StockItem>',	'gtk-quit'],
		[ "/$EditStock/$PrefsStock" , undef,			sub { PreferencesWindow($PrefsStock) },		1,	'<StockItem>', 'gtk-preferences' ],
		[ "/$HelpStock/$AboutStock" ,undef,			\&AboutBox,		0,	'<StockItem>',	'gtk-about'],
	);

	push(@MenuItems,@AdditionalItems);

	# 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();
	eval
	{
		$UpcomingEventsWidget->set_cursor_visible(false);
	};
	if ($@)
	{
		DPIntWarn('Gtk2 doesn\'t appear to support set_cursor_visible(). Please send a bug report with the output of "dayplanner --debuginfo" to the Day Planner developers');
	}
	$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();

	# Let plugins add buttons
	$plugin->set_tempvar('Toolbar',$Toolbar);
	$plugin->set_tempvar('Tooltips',$Tooltips);
	$plugin->signal_emit('BUILD_TOOLBAR');


	# 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(NoGui?);
sub MainInit
{
	Assert(scalar(@_) < 2);
	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

	if (not -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;
				$Chomped =~ s/\n/ || /;
				# This is the DP perl exception handler
				DPIntWarn("An exception occurred: $Chomped");
				# Display an error
				if ($i18n)
				{
					# TRANSLATORS: An exception here is always a bug in Day Planner. This message is displayed when an exception
					# 	occurs and the i18n subsystem was successfully initialized beforehand (which it should be in all cases unless
					# 	the bug resides in the i18n subsystem). %(exception) is the exception text, which is not localized.
					DPError($i18n->get_advanced("An exception has occurred. This reflects a bug in Day Planner. Day Planner may not work properly after this until it is restarted.\n\nPlease make sure you are running the latest version of Day Planner. If you are, save the following exception text and report the problem to the Day Planner developers by selecting 'Help' -> 'Report a Bug' in the main Day Planner window.\n\nException: %(exception)", { exception => $Exception }));
				}
				else
				{
					DPError("An exception has occurred. This reflects a bug in Day Planner. Day Planner may not work properly after this until it is restarted.\n\nPlease make sure you are running the latest version of Day Planner. If you are, save the following exception text and report the problem to the Day Planner developers by selecting 'Help' -> 'Report a Bug' in the main Day Planner window.\n\nException: $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
			{ 
				my $m = shift;
				chomp($m);
				if ($m =~ /^ALIVE_ONDISPLAY/)
				{
					exit(0);
				}
				return });
		# Send an alive request
		$Client->client_send('ALIVE '.$ENV{DISPLAY});
		if (@ARGV)
		{
			foreach(@ARGV)
			{
				$Client->client_send("IMPORT_DATA $_");
			}
			exit(0);
		}
		AlreadyRunningDP($Client);
		$IPC_Socket = DP::GeneralHelpers::IPC->new_server("$SaveToDir/ipcsocket",\&IPC_Handler);
	}
	# Init plugins
	init_plugins();
	# Draw the main window
	DrawMainWindow();
	$MainWindow->show() if not $FirstStartup;
	GTK_Flush();
	# Load the config
	LoadConfig();
	# Import data as told on the commandline
	if (@ARGV)
	{
		foreach(@ARGV)
		{
			IPC_Handler("IMPORT_DATA $_");
		}
	}
	# Initialize the daemon
	if (!$NoDaemon)
	{
		Assert(DaemonInit());
	}
	# Finally, initialize HTTP subscriptions if needed
	LoadHTTPSubscriptions();
	# Add autostart if needed
	if (not defined($InternalConfig{AddedAutostart}) or $InternalConfig{AddedAutostart} ne '1')
	{
		DP_AddAutostart();
		$InternalConfig{AddedAutostart} = 1;
	}
	# Set the redraw timer and populate the upcoming events widget
	PopulateUpcomingEvents();
	Set_DayChangeTimer();

	$plugin->set_var('MainWindow',$MainWindow);
	$plugin->set_var('CalendarWidget',$CalendarWidget);
	$plugin->signal_emit('INIT');
	return true;
}

# 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('','--nodaemon','Run without the Day Planner daemon. Useful on embedded systems.');
		PrintHelp('', '--exportical', 'Export the Day Planner data in the iCalendar format to the filename supplied.');
		PrintHelp('', '--exporthtml', 'Export the Day Planner data in the HTML format to the 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";
		if ($VersionName eq 'GIT')
		{
			print " (git snapshot)\n";
		}
		else
		{
			print " ($VersionName)\n";
		}
		exit(0);
	},
	'debuginfo' => sub
	{
		GetDebugInfo(false);
		exit(0);
	},
	'test|t:s' => sub {
		shift;
		my $prefix = shift;
		if ($prefix && $prefix =~ /(\s|\/)/)
		{
			die("The parameter for --test needs to be a string with no spaces and no /\n");
		}
		elsif (not defined $prefix)
		{
			$prefix = '';
		}
		elsif ($prefix =~ /\D/)
		{
			$prefix = '_'.$prefix;
		}
		$SaveToDir = DetectConfDir();
		$SaveToDir .= "/debug$prefix";
		my $Dir = $SaveToDir;
		$Dir =~ s/^$ENV{HOME}/~/g;
		DPIntInfo("Running in test mode (using $Dir)");
	},
	'confdir=s' => sub {
		if (-e $_[1]) {
			if (not -d $_[1]) {
				die "$_[1] is not a directory\n";
			}
			if (not -w $_[1]) {
				die "$_[1] is not writeable\n";
			}
		}
		$SaveToDir = $_[1];
	},
	's|shutdaemon' => \$ShutdownDaemon,
	'nodaemon' => \$NoDaemon,
	# The export functions that call CLI_Export() needs to be seperate because
	# CLI_Export does some magic on the commandline parameter name and gets
	# confused when it gets all the options available.
	'exportical|exportics=s' => \&CLI_Export,
	'exportics_dps=s' => sub {
		CLI_Export(@_);
	},
	'exporthtml=s' => \&CLI_Export,
	'exportphp=s' => \&CLI_Export,
	'importical|importics=s' => \&CLI_Import,
) or die "Run $0 --help for more information\n";

my $Gtk2_Initialized = 0;

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

__END__
=head1 NAME

Day Planner - a simple Day Planner for Gtk2/GNOME.

=head1 SYNOPSIS

B<dayplanner> [I<OPTIONS>]

=head1 DESCRIPTION

Day Planner is a program designed to help you easily plan and manage your
time. It can manage appointments, birthdays and more and makes sure you
remember your appointments by displaying reminders.

=head1 OPTIONS

=over

=item B<-h, --help>

Display the help screen.

=item B<-v, --version>

Display version information.

=item B<-d, --debuginfo>

Display information useful for debugging. You should include the output
of this command with any bug report.

=item B<-t, --test> I<N>

Start Day Planner in "test" mode. This starts up a Day Planner instance
completely separate from your real instance, so that no data will be touched.
This is mainly useful for testing unstable versions of Day Planner.

I<--test> can take an optional argument (I<N>) which is a number. This number
specifies the test instance to use, which allows you to have many different
instances for different purposes.

=item B<--confdir> I<DIR>

Use the directory I<DIR> instead of the default Day Planner configuration
directory. See also I<--test>.

=item B<-s, --shutdaemon>

Shut down the Day Planner daemon when closing Day Planner.

=item B<--nodaemon>

Run Day Planner without the Day Planner daemon. This is useful on systems
where memory is low, primarily embedded systems. You should be somewhat
careful with running in this mode because the daemon is the component
that launches the notifier, if it isn't running then you won't recieve
any notifications.

=item B<--exportical> I<FILE>

Export Day Planner calendar data in the iCalendar format to I<FILE>.

=item B<--importical> I<FILE>

Import iCalendar data from I<FILE>.

=item B<--exporthtml> I<DIR>

Export Day Planner data as HTML to I<DIR>. I<DIR> should be empty before you issue this
command (although Day Planner will happily overwrite data in the directory
if it isn't).

=back

=head1 HELP/SUPPORT

See L<http://www.day-planner.org/index.php/help>

=head1 AUTHOR

Eskild Hustvedt I<<eskild
at zerodogg
dot
org>>

=head1 FILES

CONFDIR is XDG_CONFIG_PATH/dayplanner by default. XDG_CONFIG_PATH is ~/.config
by default. So on most installations CONFDIR is ~/.config/dayplanner/. See
I<--debuginfo> to see which path this install is using.

These paths are altered by the I<--confdir> and I<--test>
commandline arguments.

=over

=item I<CONFDIR/dayplanner.conf>

The configuration file.

=item I<CONFDIR/calendar.ics>

The calendar file.

=item I<CONFDIR/state.conf>

The internal state file, you should not edit this.

=item I<CONFDIR/holidays>

The holiday definitions file. Feel free to edit it.

=item I<CONFDIR/daemon_state.conf>

The daemon's internal state file. You really should not edit this.

=item I<CONFDIR/daemon.log> I<CONFDIR/services.log>

The daemon and services logfiles. The latter may not exist on some setups.

=item I<CONFDIR/Daemon_Socket> I<CONFDIR/ipcsocket>

The daemon's communication socket, and Day Planner's IPC sockett

=back

=head1 SEE ALSO

For additional program documentation: 
L<dayplanner-daemon(1)> L<dayplanner-notifier(1)>

For API documentation:
L<DP::iCalendar> L<DP::iCalendar::Manager> L<Date::HolidayParser>

=head1 LICENSE AND COPYRIGHT

Copyright (C) Eskild Hustvedt 2006-2012

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 L<http://www.gnu.org/licenses/>.
