#!/usr/bin/perl
# Gournal - A notetaking app for TabletPCs running linux
# Version 0.2
# Copyright (C) 2004 Chris Debenham <chris@adebenham.com>
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#

use strict;
use Gtk2 -init;
use Gtk2::GladeXML;
use Gtk2::Helper;
use Gnome2::Canvas;
use IO::Socket;
use Compress::Zlib;
use XML::Mini::Document;

use constant TRUE  => 1;
use constant FALSE => 0;

# Brush Types
use constant NORMAL => 0;
use constant TEXT   => 1;
use constant DATE   => 2;
use constant HILITE => 3;
use constant ERASE  => 4;
use constant IMAGE  => 5;

my $sharedir  = "/usr/share/gournal/";
my $gladefile = $sharedir . "gournal.glade";
my $basedir   = $ENV{HOME} . "/.gournal/";
my $svgdir    = $basedir . "svg/";
my $prefs     = $basedir . "config";

my $oldx           = -100;
my $oldy           = -100;
my $movement       = FALSE;
my $brush_width    = 2;
my $brush_color    = 0;
my $brush_type     = NORMAL;
my $canvasText     = 0;
my $canvasImage    = 0;
my $changed_status = FALSE;
my $PORT           = 5505;
my $client_active  = FALSE;
my $server_active  = FALSE;

my (%svg,   %canvasitem);
my (@xv,    @yv, @line, @hosts, @favourites);
my ($group, $texttag, $prefsxml, $selecteditem, $selecteditem_color, $subsec,
	$io_id);
my $lastid = 0;

# Set defaults
my $font       = "Sans 40";
my $width      = 900;
my $height     = 1300;
my $spacing    = 40;
my $tmpfont    = $font;
my $tmpwidth   = $width;
my $tmpheight  = $height;
my $tmpspacing = $spacing;

# Load GUI
my $mainxml = Gtk2::GladeXML->new($gladefile, 'windowMain');
$mainxml->get_widget('notebookMain')->remove_page(0);
$mainxml->signal_autoconnect_from_package('');

if (-e $basedir) {
	if (!-e $svgdir) {
		mkdir $svgdir, 0777;
	}
} else {
	mkdir $basedir, 0777;
	mkdir $svgdir,  0777;
}

$PORT = $ARGV[0];
my $OUTPORT = $ARGV[1];
load_prefs();
opendir(DIR, $svgdir);
my $found = FALSE;
foreach (sort (readdir(DIR))) {
	if ($_ =~ /\.svg.gz$/) {
		load_page($_);
		$found = TRUE;
	}
}
closedir DIR;
if (!$found) {
	add_page();
}

set_page(0);

$mainxml->get_widget('notebookMain')
  ->signal_connect_after(switch_page => \&switch_page, undef);

$mainxml->get_widget('notebookMain')->set_current_page(0);

$mainxml->get_widget('windowMain')->maximize;
$mainxml->get_widget('buttonBlack')->set_active(TRUE);

turn_on_server();

# Gtk event loop
Gtk2->main;

# Should never get here
exit(0);

sub load_page {
	my ($filename) = @_;
	print "Loading " . $filename . "\n";

	my $pagenum = $mainxml->get_widget('notebookMain')->get_current_page + 1;
	my $scroll  = Gtk2::ScrolledWindow->new;
	$scroll->set_policy('automatic', 'automatic');
	$scroll->show;

	my $tmpname = $filename;
	$tmpname =~ s/\.svg.gz$//g;
	my $label = Gtk2::Label->new($tmpname);

	my $canvas = new_canvas($filename);

	$scroll->add($canvas);

	$mainxml->get_widget('notebookMain')
	  ->insert_page($scroll, $label, $pagenum);
	$mainxml->get_widget('notebookMain')->set_current_page($pagenum);
}

sub finish_load {
	my ($filename, $canvas) = @_;
	my $gz;
	print "Finishing loading " . $filename . "\n";
	if ($gz = gzopen($svgdir . $filename, "rb")) {
		my $xml = "";
		while ($gz->gzread($_)) {
			$xml .= $_;
		}
		$gz->gzclose;
		$svg{$filename} = XML::Mini::Document->new();
		$svg{$filename}->parse($xml);
		my $svg2             = $svg{$filename}->getElement('svg');
		my $topLevelChildren = $svg2->getAllChildren();
		foreach my $child (@{$topLevelChildren}) {

			if ($child->{'_attributes'}->{'visibility'}
				&& $child->{'_attributes'}->{'visibility'} eq "hidden")
			{

				# ignore
			} else {

				load_item($filename, $canvas, $child);
			}
		}
	} else {
		$svg{$filename} = XML::Mini::Document->new();
		my $header = $svg{$filename}->getRoot()->header('xml');
		$header->attribute('version', '1.0');
		$svg{$filename}->getRoot()->comment('Created with Gournal');
		$svg{$filename}->getRoot()
		  ->docType(
'svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"'
		);
		my $tmpsvg = $svg{$filename}->getRoot()->createChild('svg');
		$tmpsvg->attribute('width',  $width);
		$tmpsvg->attribute('height', $height);
		$tmpsvg->attribute('xmlns', "http://www.w3.org/2000/svg");
		$tmpsvg->attribute('xmlns:xlink', "http://www.w3.org/1999/xlink");
		$tmpsvg->attribute('version', "1.1");
	}
}

sub save_page {
	print "Saving\n";
	finish_text();
	foreach (keys %svg) {
		my $gz = gzopen($svgdir . $_, "wb");
		$gz->gzwrite($svg{$_}->toString);
		$gz->gzclose;
		print $_. " Saved\n";
	}
	$changed_status = FALSE;
}

sub clear_page {
	my $pagenum = $mainxml->get_widget('notebookMain')->get_current_page;
	my $canvas  =
	  $mainxml->get_widget('notebookMain')->get_nth_page($pagenum)->get_child;
	my $filename = $canvas->{user_data};
	$canvas->destroy;
	$canvas = new_canvas($filename);
	$mainxml->get_widget('notebookMain')->get_nth_page($pagenum)->add($canvas);

	$svg{$filename} = XML::Mini::Document->new();
	my $header = $svg{$filename}->getRoot()->header('xml');
	$header->attribute('version', '1.0');
	$svg{$filename}->getRoot()->comment('Created with Gournal');
	$svg{$filename}->getRoot()
	  ->docType(
'svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"'
	);
	my $tmpsvg = $svg{$filename}->getRoot()->createChild('svg');
	$tmpsvg->attribute('width',  $width);
	$tmpsvg->attribute('height', $height);
	$changed_status = TRUE;
}

sub add_page {
	my @time     = localtime(time);
	my $filename =
	  sprintf("%04d-%02d-%02d@%02d:%02d.svg.gz", ($time[5] + 1900),
		($time[4] + 1), $time[3], $time[2], $time[1], $time[0]);
	load_page($filename);
	$changed_status = FALSE;
}

sub button_press_event {
	my ($widget, $event) = @_;
	my ($x,      $y)     = $event->coords;
	my ($cx,     $cy)    = $widget->window_to_world($x, $y);
	if ($event->button == 3) {
		brush_erase();
	}
	if ($event->button == 1 || $event->button == 3) {
		my $id = time;
		if (int($lastid) == $id) {
			$id = $lastid + 0.1;
		}
		$lastid = $id;
		if ($brush_type == DATE) {
			$changed_status = TRUE;
			my @time = localtime(time);
			my $date = sprintf(
				"%d:%02d %d/%d/%02d", $time[2],
				$time[1],             $time[3],
				($time[4] + 1),       ($time[5] - 100)
			);
			$canvasitem{$widget->{user_data}}->{$id} =
			  Gnome2::Canvas::Item->new(
				$widget->root, 'Gnome2::Canvas::Text', fill_color => 'black',
				x      => $cx,
				y      => $cy,
				font   => $font,
				text   => $date,
				anchor => 'north-west'
			);
			my $filename = $widget->{user_data};
			my $tag = $svg{$filename}->getElement('svg')->createChild('text');
			$tag->attribute('x',     $cx);
			$tag->attribute('y',     $cy);
			$tag->attribute('name',  $id);
			$tag->attribute('style', "font:" . $font);
			$tag->text($date);
		} elsif ($brush_type == TEXT) {
			$changed_status = TRUE;
			$canvasText     = $id;
			$canvasitem{$widget->{user_data}}->{$id} =
			  Gnome2::Canvas::Item->new(
				$widget->root, 'Gnome2::Canvas::Text', x => $cx,
				y          => $cy,
				text       => "",
				anchor     => 'north-west',
				fill_color => 'black',
				font       => $font
			);
			my $filename = $widget->{user_data};
			$texttag = $svg{$filename}->getElement('svg')->createChild('text');
			$texttag->attribute('x',     $cx);
			$texttag->attribute('y',     $cy);
			$texttag->attribute('name',  $id);
			$texttag->attribute('style', "font:" . $font);
			$texttag->text("");
		} elsif ($brush_type == ERASE) {
		} elsif ($brush_type == IMAGE) {
		} else {
			$changed_status = TRUE;
			my @time = localtime(time);
			my $root = $widget->root;
			$group = Gnome2::Canvas::Item->new(
				$root, 'Gnome2::Canvas::Group', x => 0,
				y => 0
			);
			draw_brush($widget, $event->coords);
			my ($x, $y) = $event->coords;
			my ($cx, $cy) = $widget->window_to_world($x, $y);
			@xv       = ($cx);
			@yv       = ($cy);
			$movement = FALSE;
		}
	}
	return TRUE;
}

sub button_release_event {
	my ($widget, $event) = @_;
	if (($event->button == 1 || $event->button == 3)
		&& ($brush_type == NORMAL || $brush_type == HILITE))
	{
		if (!$movement) {
			draw_brush($widget, $oldx + 1, $oldy + 1);
		}
		$oldx = -100;
		$oldy = -100;
		my $filename  = $widget->{user_data};
		my $path      = $svg{$filename}->getElement('svg')->createChild('path');
		my $brush_col = $brush_color;
		$brush_col = int($brush_color / 256);
		$brush_col = sprintf('#%06X', $brush_col);
		my $brush_trans = $brush_color;
		$brush_trans = (($brush_trans / 256) - (int($brush_trans / 256)));
		my $style = sprintf (
			'fill:none;stroke:'
			. $brush_col
			. ';stroke-opacity:'
			. $brush_trans
			. ';stroke-linecap:round'
			. ';stroke-linejoin:round'
			. ';stroke-width:'
			. $brush_width . ";");
		$path->attribute('style', $style );
		my $id = time;

		if (int($lastid) == $id) {
			$id = $lastid + 0.1;
		}
		$lastid = $id;
		$path->attribute('name', $id);
		my $points = "";
		my $i      = 0;

		$points = "M " . $xv[0] . " " . $yv[0] . " ";
		for ($i = 1 ; $i < @xv ; $i++) {
			$points .= "L " . $xv[$i] . " " . $yv[$i] . " ";
		}
		$path->attribute('d', $points);
		if ($client_active) {
			send_item($path, $filename);
		}

		$group->destroy;
		$group = undef;
		my $root = $widget->root;
		$canvasitem{$filename}->{$id} = Gnome2::Canvas::Item->new(
			$root, 'Gnome2::Canvas::Line', points => \@line,
			cap_style       => 'round',
			join_style      => 'round',
			fill_color_rgba => $brush_color,
			width_units     => $brush_width
		);
		$canvasitem{$filename}->{$id}->{user_data} = $id;
		@line = ();

	}
	if ($event->button == 3) {
		$mainxml->get_widget('buttonBlack')->set_active(TRUE);
	}
	if ($brush_type == DATE) {
		$mainxml->get_widget('buttonBlack')->set_active(TRUE);
	}
	if ($brush_type == ERASE) {
		my $filename = $widget->{user_data};
		if ($selecteditem) {
			delete_item($filename, $selecteditem);
		}
		$selecteditem = FALSE;
	} elsif ($brush_type == IMAGE) {
		my ($x, $y) = $event->coords;
		my ($cx, $cy) = $widget->window_to_world($x, $y);
		my $id = time;
		if (int($lastid) == $id) {
			$id = $lastid + 0.1;
		}
		$lastid = $id;
		$canvasitem{$widget->{user_data}}->{$id} = $canvasImage;
		my $filename = $widget->{user_data};
		my $tag      = $svg{$filename}->getElement('svg')->createChild('image');
		$tag->attribute('x',          $cx);
		$tag->attribute('y',          $cy);
		$tag->attribute('width',      $canvasImage->get('width'));
		$tag->attribute('height',     $canvasImage->get('height'));
		$tag->attribute('name',       $id);
		$tag->attribute('xlink:href', $canvasImage->{user_data});
		$mainxml->get_widget('buttonBlack')->set_active(TRUE);
	}

	return TRUE;
}

# Draw a rectangle on the screen
sub draw_brush {
	my ($widget, $x, $y) = @_;
	my ($cx2, $cy2) = $widget->window_to_world($x, $y);
	$x = $cx2;
	$y = $cy2;
	push @xv,   $x;
	push @yv,   $y;
	push @line, $x, $y;

	if (($oldx == -100) && ($oldy == -100)) {
		$oldx = $x;
		$oldy = $y;
	}

	Gnome2::Canvas::Item->new(
		$group,
		'Gnome2::Canvas::Line',
		points          => [$oldx,      $oldy, $x, $y],
		cap_style       => 'round',
		join_style      => 'round',
		fill_color_rgba => $brush_color,
		width_units     => $brush_width
	);

	$oldx = $x;
	$oldy = $y;
}

sub motion_notify_event {
	my ($widget, $event) = @_;

	my ($x, $y, $state);

	if ($event->is_hint) {
		(undef, $x, $y, $state) = $event->window->get_pointer;
	} else {
		$x     = $event->x;
		$y     = $event->y;
		$state = $event->state;
	}
	if ((grep (/button1-mask/, @$state) || grep (/button3-mask/, @$state))
		&& ($brush_type == NORMAL || $brush_type == HILITE))
	{
		$movement = TRUE;
		draw_brush($widget, $x, $y);
	}

	if ($brush_type == ERASE) {
		my $pagenum = $mainxml->get_widget('notebookMain')->get_current_page;
		my $canvas  =
		  $mainxml->get_widget('notebookMain')->get_nth_page($pagenum)
		  ->get_child;
		my $filename = $canvas->{user_data};
		my $item = $canvas->get_item_at($x, $y);
		if (defined $item->{user_data}) {
			if ($selecteditem) {
				$canvasitem{$filename}->{$selecteditem}->set(
					fill_color_rgba => $selecteditem_color);
			}
			$selecteditem       = $item->{user_data};
			$selecteditem_color =
			  $canvasitem{$filename}->{$selecteditem}->get('fill_color_rgba');
			$canvasitem{$filename}->{$selecteditem}->set(
				fill_color_rgba => 0xff0000ff);
			if (grep (/button1-mask/, @$state)
				|| grep (/button3-mask/, @$state))
			{
				delete_item($filename, $selecteditem);
				$selecteditem = FALSE;
			}
		} else {
			if ($selecteditem) {
				$canvasitem{$filename}->{$selecteditem}->set(
					fill_color_rgba => $selecteditem_color);
				$selecteditem = FALSE;
			}
		}
	} elsif ($brush_type == IMAGE) {
		my ($cx, $cy) = $widget->window_to_world($x, $y);
		$canvasImage->set('x' => $cx);
		$canvasImage->set('y' => $cy);
	}

	return TRUE;
}

sub new_canvas {
	my ($filename) = @_;

	# Create Canvas
	my $canvas = Gnome2::Canvas->new_aa();
	$canvas->set_scroll_region(0, 0, 600, 800);
	$canvas->set_center_scroll_region(FALSE);
	$canvas->set_pixels_per_unit(1);
	$canvas->{user_data} = $filename;
	$canvas->signal_connect(
		motion_notify_event => \&motion_notify_event,
		undef
	);
	$canvas->signal_connect(button_press_event => \&button_press_event, undef);
	$canvas->signal_connect(
		button_release_event => \&button_release_event,
		undef
	);
	$canvas->signal_connect_after(key_release_event => \&key_release_event,
		undef);

	$canvas->show;
	my $root = $canvas->root;

	Gnome2::Canvas::Item->new(
		$root, 'Gnome2::Canvas::Rect', outline_color => 'black',
		fill_color => 'white',
		x1         => 0,
		y1         => 0,
		x2         => $width - 100,
		y2         => $height - 100
	);

	Gnome2::Canvas::Item->new(
		$root, 'Gnome2::Canvas::Line',
		points      => [40,   0, 40, $height - 100],
		fill_color  => 'blue',
		width_units => .5
	);

	for (my $i = 40 ; $i < ($height - 100) ; $i = $i + $spacing) {
		Gnome2::Canvas::Item->new(
			$root, 'Gnome2::Canvas::Line',
			points      => [0,    $i, $width - 100, $i],
			fill_color  => 'blue',
			width_units => .5
		);
	}

	return $canvas;
}

sub close_main {
	my $xml = Gtk2::GladeXML->new($gladefile, 'dialogConfirmClose');
	$xml->signal_autoconnect_from_package('');
	if (!$changed_status) {
		$xml->get_widget('buttonCloseSave')->hide();
	}
	return TRUE;
}

sub destroy_main {
	if ($changed_status) {
		my $xml = Gtk2::GladeXML->new($gladefile, 'dialogConfirmClose');
		$xml->signal_autoconnect_from_package('');
		$xml->get_widget('buttonCloseCancel')->hide();
	} else {
		exit_confirmed();
	}
	return TRUE;
}

sub delete_page {
	my $pagenum = $mainxml->get_widget('notebookMain')->get_current_page;
	my $canvas  =
	  $mainxml->get_widget('notebookMain')->get_nth_page($pagenum)->get_child;
	my $filename = $canvas->{user_data};
	my $name     = $filename;
	$name =~ s/\.svg.gz$//g;
	my $xml = Gtk2::GladeXML->new($gladefile, 'dialogDelete');
	$xml->signal_autoconnect_from_package('');
	$xml->get_widget('labelDelete')
	  ->set_text(
		"Are you sure you want to\ndelete the page named\n" . $name . "?");
	$xml->get_widget('dialogDelete')->{user_data} = $filename;
}

sub do_delete_page {
	my ($widget) = @_;
	my $filename = $widget->get_toplevel->{user_data};
	print "Deleting " . $filename . "\n";
	cancel_dialog($widget);
	my $pagenum = $mainxml->get_widget('notebookMain')->get_current_page;
	$mainxml->get_widget('notebookMain')->remove_page($pagenum);
	delete $svg{$filename};
	system "rm " . $svgdir . $filename . "*";

	#unlink $svgdir . $filename;
}

sub rename_page {
	my $pagenum = $mainxml->get_widget('notebookMain')->get_current_page;
	my $canvas  =
	  $mainxml->get_widget('notebookMain')->get_nth_page($pagenum)->get_child;
	my $filename = $canvas->{user_data};
	my $name     = $filename;
	$name =~ s/\.svg.gz$//g;
	my $xml = Gtk2::GladeXML->new($gladefile, 'dialogRename');
	$xml->signal_autoconnect_from_package('');
	$xml->get_widget('labelRename')
	  ->set_text("What do you want to\nrename " . $name . " to?");
	$xml->get_widget('dialogRename')->{user_data} = $filename;
	$xml->get_widget('entryRename')->set_text($name);
	my $response = $xml->get_widget('dialogRename')->run();

	if ($response eq "cancel") {
		$xml->get_widget('dialogRename')->destroy();
	} else {
		my $newtext = $xml->get_widget('entryRename')->get_text();
		$canvas->{user_data} = $newtext;
		$mainxml->get_widget('notebookMain')
		  ->set_tab_label_text(
			$mainxml->get_widget('notebookMain')->get_nth_page($pagenum),
			$newtext);
		rename($svgdir . $filename, $svgdir . $newtext . ".svg.gz");
		$xml->get_widget('dialogRename')->destroy();
		$svg{$newtext} = $svg{$filename};
		delete $svg{$filename};
		$canvasitem{$newtext} = $canvasitem{$filename};
		delete $canvasitem{$filename};
	}
}

sub exit_confirmed {
	my $pagenum = 0;
	while (Gtk2->events_pending) {
		Gtk2->main_iteration;
	}
	Gtk2->main_quit;
	return FALSE;
}

sub exit_and_save {
	save_page();
	exit_confirmed();
}

sub cancel_dialog {
	my ($widget) = @_;
	$widget->get_toplevel->destroy();
}

sub brush_fine {
	if (toggle_thick("buttonFine")) {
		$brush_width = 1;
	}
}

sub brush_medium {
	if (toggle_thick("buttonMedium")) {
		$brush_width = 2;
	}
}

sub brush_heavy {
	if (toggle_thick("buttonHeavy")) {
		$brush_width = 4;
	}
}

sub brush_fat {
	if (toggle_thick("buttonThick")) {
		$brush_width = 20;
	}
}

sub tool_black {
	if (toggle_tool("buttonBlack")) {
		finish_text();
		$brush_color = 0x000000ff;
		$brush_type  = NORMAL;
		$mainxml->get_widget('buttonMedium')->set_active(TRUE);
	}
}

sub tool_erase {
	if (toggle_tool("buttonWhiteout")) {
		finish_text();
		$brush_color = 0xffffffff;
		$brush_type  = NORMAL;
		$mainxml->get_widget('buttonThick')->set_active(TRUE);
	}
}

sub tool_delete {
	if (toggle_tool("buttonDelete")) {
		finish_text();
		$brush_type = ERASE;
	}
}

sub tool_hilite {
	if (toggle_tool("buttonHighlite")) {
		finish_text();
		$brush_color = 0xffff0080;
		$brush_type  = HILITE;
		$mainxml->get_widget('buttonThick')->set_active(TRUE);
	}
}

sub zoom_in {
	my $pagenum = $mainxml->get_widget('notebookMain')->get_current_page;
	my $canvas  =
	  $mainxml->get_widget('notebookMain')->get_nth_page($pagenum)->get_child;
	$canvas->set_pixels_per_unit(2);
}

sub zoom_out {
	my $pagenum = $mainxml->get_widget('notebookMain')->get_current_page;
	my $canvas  =
	  $mainxml->get_widget('notebookMain')->get_nth_page($pagenum)->get_child;
	$canvas->set_pixels_per_unit(1);
}

sub add_text {
	if (toggle_tool("buttonText")) {
		finish_text();
		$brush_type = TEXT;
	}
}

sub add_date {
	if (toggle_tool("buttonTime")) {
		finish_text();
		$brush_type = DATE;
	}
}

sub key_release_event {
	my ($widget, $event) = @_;
	if (($brush_type == TEXT) && $canvasText != 0) {
		my $canvas =
		  $mainxml->get_widget('notebookMain')
		  ->get_nth_page($mainxml->get_widget('notebookMain')->get_current_page)
		  ->get_child;
		my $filename = $canvas->{user_data};
		if ($event->keyval == 65288) {
			$canvasitem{$filename}->{$canvasText}->set(
				text => substr(
					$canvasitem{$filename}->{$canvasText}->get('text'), 0,
					-1
				)
			);
		} else {
			$canvasitem{$filename}->{$canvasText}->set(
				text => $canvasitem{$filename}->{$canvasText}->get('text')
				. chr(Gtk2::Gdk->keyval_to_unicode($event->keyval)))
			  ;    # Potential problem if key needs more than 1 byte
		}
	}
}

sub finish_text {
	if (defined $texttag) {
		my $canvas =
		  $mainxml->get_widget('notebookMain')
		  ->get_nth_page($mainxml->get_widget('notebookMain')->get_current_page)
		  ->get_child;
		my $filename = $canvas->{user_data};
		$texttag->text($canvasitem{$filename}->{$canvasText}->get('text'));
		undef $texttag;
	}
}

sub undo_last {
	my $canvas =
	  $mainxml->get_widget('notebookMain')
	  ->get_nth_page($mainxml->get_widget('notebookMain')->get_current_page)
	  ->get_child;
	my $filename = $canvas->{user_data};
	my $s        = ($canvasitem{$filename});
	my @keys     = sort keys(%$s);
	my $lastkey  = pop (@keys);
	delete_item($filename, $lastkey);
	$changed_status = TRUE;
}

sub redo_last {
	my $canvas =
	  $mainxml->get_widget('notebookMain')
	  ->get_nth_page($mainxml->get_widget('notebookMain')->get_current_page)
	  ->get_child;
	my $filename         = $canvas->{user_data};
	my $svg2             = $svg{$filename}->getElement('svg');
	my $topLevelChildren = $svg2->getAllChildren();
	my @deleted          = ();
	foreach my $child (@{$topLevelChildren}) {
		if ($child->{'_attributes'}->{'visibility'}
			&& $child->{'_attributes'}->{'visibility'} eq "hidden")
		{
			push @deleted, $child->{'_attributes'}->{'name'};
		}
	}
	my @tmp     = sort @deleted;
	my $itemnum = pop @tmp;
	if ($itemnum) {
		foreach my $child (@{$topLevelChildren}) {
			if ($child->{'_attributes'}->{'name'} eq $itemnum) {
				$child->attribute('visibility', "visible");
				$child->attribute('name',       time);
				load_item($filename, $canvas, $child);
			}
		}
	}
	$changed_status = TRUE;
}

sub set_page {
	my ($page) = @_;
	my $canvas =
	  $mainxml->get_widget('notebookMain')->get_nth_page($page)->get_child;
	my $filename = $canvas->{user_data};
	if (!exists $svg{$filename}) {
		finish_load($filename, $canvas);
	}
}

sub switch_page {
	my $pagenum = $mainxml->get_widget('notebookMain')->get_current_page;
	set_page($pagenum);
}

sub load_item {
	my ($filename, $canvas, $child) = @_;
	my $root = $canvas->root;
	if ($child->{'_name'} eq "path") {
		my ($styles);
		my $tmp = $child->{'_attributes'}->{'style'};
		my @style = split (/;/, $tmp);
		foreach (@style) {
			my @tmp2 = split (/:/, $_);
			$styles->{$tmp2[0]} = $tmp2[1];
		}
		my $tmppoints = $child->{'_attributes'}->{'d'};
		$tmppoints =~ s/[ML] //g;
		my @points = split (/ /, $tmppoints);
		my $brush_col = $styles->{'stroke'};
		$styles->{'stroke-opacity'} =~ s/,/./g;
		$brush_col =~ s/#//g;
		$brush_col = (hex($brush_col) + $styles->{'stroke-opacity'}) * 256;
		$canvasitem{$filename}->{$child->{'_attributes'}->{'name'}} =
		  Gnome2::Canvas::Item->new(
			$root, 'Gnome2::Canvas::Line', cap_style => 'round',
			join_style      => 'round',
			points          => \@points,
			fill_color_rgba => $brush_col,
			width_units     => $styles->{'stroke-width'}
		);
		$canvasitem{$filename}->{$child->{'_attributes'}->{'name'}}->{user_data}
		  = $child->{'_attributes'}->{'name'};
	} elsif ($child->{'_name'} eq "polyline") {
		my ($styles);
		my $tmp = $child->{'_attributes'}->{'style'};
		my @style = split (/; /, $tmp);
		foreach (@style) {
			my @tmp2 = split (/: /, $_);
			$styles->{$tmp2[0]} = $tmp2[1];
		}
		my $tmppoints = $child->{'_attributes'}->{'points'};
		$tmppoints =~ s/^points: //g;
		my @points = split (/[ ,]/, $tmppoints);
		$canvasitem{$filename}->{$child->{'_attributes'}->{'name'}} =
		  Gnome2::Canvas::Item->new(
			$root, 'Gnome2::Canvas::Line', cap_style => 'round',
			join_style      => 'round',
			points          => \@points,
			fill_color_rgba => $styles->{'stroke'},
			width_units     => $styles->{'stroke-width'}
		);
		$canvasitem{$filename}->{$child->{'_attributes'}->{'name'}}->{user_data}
		  = $child->{'_attributes'}->{'name'};
	} elsif ($child->{'_name'} eq "text") {
		my ($styles);
		my @style = split (/; /, $child->{'_attributes'}->{'style'});
		foreach (@style) {
			my @tmp2 = split (/: /, $_);
			$styles->{$tmp2[0]} = $tmp2[1];
		}
		my $data = $child->{'_children'}[0]->{'_contents'};
		chomp($data);
		$canvasitem{$filename}->{$child->{'_attributes'}->{'name'}} =
		  Gnome2::Canvas::Item->new(
			$root, 'Gnome2::Canvas::Text', fill_color => 'black',
			x      => $child->{'_attributes'}->{'x'},
			y      => $child->{'_attributes'}->{'y'},
			font   => $styles->{'font'},
			text   => $data,
			anchor => 'north-west'
		);
		$canvasitem{$filename}->{$child->{'_attributes'}->{'name'}}->{user_data}
		  = $child->{'_attributes'}->{'name'};
	} elsif ($child->{'_name'} eq "image") {
		my $filename = $child->{'_attributes'}->{'xlink:href'};
		my $im       = Gtk2::Gdk::Pixbuf->new_from_file($svgdir . $filename);
		$canvasitem{$filename}->{$child->{'_attributes'}->{'name'}} =
		  Gnome2::Canvas::Item->new(
			$root,    'Gnome2::Canvas::Pixbuf',
			"pixbuf", $im,
			"x",      $child->{'_attributes'}->{'x'},
			"y",      $child->{'_attributes'}->{'y'},
			"width",  $child->{'_attributes'}->{'width'},
			"height", $child->{'_attributes'}->{'height'},
			"anchor", 'nw',
		);

	}
}

sub choose_font {
	my $xml = Gtk2::GladeXML->new($gladefile, 'dialogFont');
	$xml->signal_autoconnect_from_package('');
	print $font. "/n";
	$xml->get_widget('dialogFont')->set_font_name($font);
	my $response = $xml->get_widget('dialogFont')->run();

	if ($response eq "cancel") {
		$xml->get_widget('dialogFont')->destroy();
	} else {
		$font = $xml->get_widget('dialogFont')->get_font_name;
		print $font. "/n";
		$xml->get_widget('dialogFont')->destroy();
	}
}

sub setup_gournal {
	$prefsxml = Gtk2::GladeXML->new($gladefile, 'dialogPrefs');
	$prefsxml->signal_autoconnect_from_package('');

	# Don't allow cancel if there is no existing prefs
	$prefsxml->get_widget('spinWidth')->set_value($width);
	$prefsxml->get_widget('spinHeight')->set_value($height);
	$prefsxml->get_widget('spinSpacing')->set_value($spacing);
	$prefsxml->get_widget('entryFont')->set_text($font);
	my $store = $prefsxml->get_widget('treeHosts')->get_model();
	if ($store) {
		$store->clear;
	} else {
		$store = Gtk2::ListStore->new('Glib::String');
		$prefsxml->get_widget('treeHosts')->set_model($store);
		my $renderer = Gtk2::CellRendererText->new;
		$renderer->set(editable          => TRUE);
		$renderer->signal_connect(edited => \&edit_favourite);
		my $column =
		  Gtk2::TreeViewColumn->new_with_attributes("Hosts", $renderer,
			text => 0);
		$prefsxml->get_widget('treeHosts')->append_column($column);
	}
	foreach my $item (@favourites) {
		$store->set($store->append, 0, $item);
		print $item. "\n";
	}
	my $response = $prefsxml->get_widget('dialogPrefs')->run();
	if ($response eq "ok") {
		open(PREFS, ">" . $prefs)
		  || die ("Couldn't create the config file : $!");
		print PREFS "Width = " . $tmpwidth . "\n";
		print PREFS "Height = " . $tmpheight . "\n";
		print PREFS "Spacing = " . $tmpspacing . "\n";
		print PREFS "Font = " . $tmpfont . "\n";
		foreach my $fav (@favourites) {
			print PREFS "Host = " . $fav . "\n";
		}
		close PREFS;
	}
	$prefsxml->get_widget('dialogPrefs')->destroy();
	load_prefs();
}

sub load_prefs {
	open(PREFS, $prefs) || setup_gournal();
	@favourites = ();
	while (<PREFS>) {
		chomp;
		my @line = split (/=/);
		$line[0] =~ s/ *$//g;
		$line[1] =~ s/^ *//g;
		if ($line[0] eq "Width") {
			$width = $line[1];
		} elsif ($line[0] eq "Height") {
			$height = $line[1];
		} elsif ($line[0] eq "Spacing") {
			$spacing = $line[1];
		} elsif ($line[0] eq "Font") {
			$font = $line[1];
		} elsif ($line[0] eq "Host") {
			push @favourites, $line[1];
		}
	}
	close PREFS;
}

sub pref_changed {
	$tmpwidth   = $prefsxml->get_widget('spinWidth')->get_value_as_int();
	$tmpheight  = $prefsxml->get_widget('spinHeight')->get_value_as_int();
	$tmpspacing = $prefsxml->get_widget('spinSpacing')->get_value_as_int();
	$tmpfont    = $prefsxml->get_widget('entryFont')->get_text();
}

sub delete_item {
	my ($filename, $id) = @_;
	if (defined $canvasitem{$filename}->{$id}) {
		$canvasitem{$filename}->{$id}->destroy;
		delete $canvasitem{$filename}->{$id};
		my $svg2             = $svg{$filename}->getElement('svg');
		my $topLevelChildren = $svg2->getAllChildren();

		foreach my $child (@{$topLevelChildren}) {
			if ($child->{'_attributes'}->{'name'}
				&& $child->{'_attributes'}->{'name'} eq $id)
			{
				$child->attribute('visibility', "hidden");
				$child->attribute('name',       time);
			}
		}
	}
}

sub show_help {
	my $tmpxml = Gtk2::GladeXML->new($gladefile, 'dialogAbout');
	$tmpxml->signal_autoconnect_from_package('');

	my $response = $tmpxml->get_widget('dialogAbout')->run();
	$tmpxml->get_widget('dialogAbout')->destroy();
}

sub io_handler {
	my ($sock, $tag) = @_;
	my $canvas   = FALSE;
	my $new_sock = $sock->accept();
	while (defined($_ = <$new_sock>)) {
		chomp;

		# Wait until the local user is finished what they are doing
		while (($oldx != -100) && ($oldy != -100) && (defined $texttag)) {
			Gtk2->events_pending();
		}
		my @line = split (/"/, $_);
		my %item;
		foreach (@line) {
			my @tmp = split (/=/, $_, 2);
			$item{$tmp[0]} = $tmp[1];
		}

		if (!exists $svg{$item{'page'}}) {
			load_page($item{'page'});
			$canvas =
			  $mainxml->get_widget('notebookMain')
			  ->get_nth_page(
				$mainxml->get_widget('notebookMain')->get_current_page)
			  ->get_child;
		} else {
			my $pagenum = 0;
			while (
				!(($canvas)
					&& ($pagenum <
						$mainxml->get_widget('notebookMain')->get_n_pages())))
			{
				$canvas =
				  $mainxml->get_widget('notebookMain')->get_nth_page($pagenum)
				  ->get_child;
				if ($canvas->{user_data} ne $item{'page'}) {
					$canvas = FALSE;
				}
				$pagenum++;
			}
			if (!$canvas) {
				load_page($item{'page'});
				$canvas =
				  $mainxml->get_widget('notebookMain')
				  ->get_nth_page(
					$mainxml->get_widget('notebookMain')->get_current_page)
				  ->get_child;
				finish_load($item{'page'}, $canvas);
			}
		}
		my $item = $svg{$item{'page'}}->getElement('svg')->createChild('path');
		foreach (keys %item) {
			if ($_ ne "page") {
				$item->attribute($_, $item{$_});
			}
		}
		load_item($item{'page'}, $canvas, $item);
	}
	close($new_sock);

	return TRUE;
}

sub send_item {
	my ($item, $filename) = @_;
	print $OUTPORT. "\n";
	foreach my $hostname (@hosts) {
		if (my $server = IO::Socket::INET->new(
				Proto    => "tcp",
				PeerAddr => $hostname,
				PeerPort => $OUTPORT
			))
		{

			my $output = 'page=' . $filename;
			my $hash   = $item->{'_attributes'};
			foreach (keys %$hash) {
				$output .= '"' . $_ . '=' . $item->{'_attributes'}->{$_};
			}
			print $server $output . "\n";
			close($server);
		}
	}
}

sub toggle_client {
	my $conxml = Gtk2::GladeXML->new($gladefile, 'dialogConnect');
	$conxml->signal_autoconnect_from_package('');
	my @tmp = sort @hosts;
	@hosts = @tmp;
	my $table = new Gtk2::Table((int(@hosts / 3) + 1), 3, TRUE);
	my @button = ();
	for (my $row = 0 ; $row <= int(@hosts / 3) ; $row++) {
		foreach my $col (0 .. 2) {
			my $itemnum = $row * 3 + $col;
			if (defined $hosts[$itemnum]) {
				$button[$itemnum] = Gtk2::ToggleButton->new($hosts[$itemnum]);
				$button[$itemnum]->set_active(TRUE);
				$table->attach($button[$itemnum], $col, $col + 1, $row,
					$row + 1, ['expand', 'fill'], ['expand', 'fill'], 0, 0);
			}
		}
	}
	$conxml->get_widget('vboxConnect')->pack_start($table, TRUE, TRUE, 0);
	$table->show_all();

	my $response = $conxml->get_widget('dialogConnect')->run();
	if ($response eq "ok") {
		my $found   = FALSE;
		my @tmplist = ();
		for my $itemnum (0 .. (@hosts - 1)) {
			if ($hosts[$itemnum] eq $conxml->get_widget('entryConnect')
				->get_text())
			{
				$found = TRUE;
			}
			if ($button[$itemnum]->get_active()) {
				if ($hosts[$itemnum] ne $conxml->get_widget('entryConnect')
					->get_text())
				{
					push @tmplist, $hosts[$itemnum];
				}
			}
		}
		if ((!$found)
			&& ($conxml->get_widget('entryConnect')->get_text() ne ""))
		{
			push @tmplist, $conxml->get_widget('entryConnect')->get_text();
		}
		@hosts = sort @tmplist;
	}
	print @hosts . "\n";
	if (@hosts == 0) {
		$client_active = FALSE;
	} else {
		$client_active = TRUE;
	}
	my $vbox = Gtk2::VBox->new(FALSE, 0);
	if ($client_active) {
		my $icon = Gtk2::Image->new_from_stock('gtk-cancel', 'large_toolbar');
		$vbox->pack_start($icon, TRUE, TRUE, 0);
		my $label = Gtk2::Label->new("Disconnect");
		$vbox->pack_start($label, TRUE, TRUE, 0);
	} else {
		my $icon = Gtk2::Image->new_from_stock('gtk-ok', 'large_toolbar');
		$vbox->pack_start($icon, TRUE, TRUE, 0);
		my $label = Gtk2::Label->new("Connect");
		$vbox->pack_start($label, TRUE, TRUE, 0);
	}
	$conxml->get_widget('dialogConnect')->destroy();
	my $child = $mainxml->get_widget('buttonClient')->get_child();
	$child->destroy();
	$mainxml->get_widget('buttonClient')->add($vbox);
	$mainxml->get_widget('buttonClient')->show_all;
}

sub toggle_server {
	my $vbox = Gtk2::VBox->new(FALSE, 0);
	if ($server_active) {
		turn_off_server();
		my $icon = Gtk2::Image->new_from_stock('gtk-no', 'large_toolbar');
		$vbox->pack_start($icon, TRUE, TRUE, 0);
		my $label = Gtk2::Label->new("Deaf");
		$vbox->pack_start($label, TRUE, TRUE, 0);
	} else {
		turn_on_server();
		my $icon = Gtk2::Image->new_from_stock('gtk-yes', 'large_toolbar');
		$vbox->pack_start($icon, TRUE, TRUE, 0);
		my $label = Gtk2::Label->new("Listening");
		$vbox->pack_start($label, TRUE, TRUE, 0);
	}
	my $child = $mainxml->get_widget('buttonServer')->get_child();
	$child->destroy();
	$mainxml->get_widget('buttonServer')->add($vbox);
	$mainxml->get_widget('buttonServer')->show_all;

}

sub turn_off_server {
	Gtk2::Helper->remove_watch($io_id);
	$mainxml->get_widget('buttonServer')->set_label("Deaf");
	$server_active = FALSE;
}

sub turn_on_server {
	my $sock = new IO::Socket::INET(
		Listen    => SOMAXCONN,
		LocalPort => $PORT,
		Resuse    => 1,
		Proto     => 'tcp'
	  )
	  or die "Can't open socket: $!";
	$sock->autoflush(1);
	$io_id =
	  Gtk2::Helper->add_watch($sock->fileno, 'in',
		sub { io_handler($sock, $io_id); });
	$server_active = TRUE;
}

sub edit_favourite {
	my ($widget, $num, $newtext) = @_;
	my $store = $prefsxml->get_widget('treeHosts')->get_model();
	my $iter  = $store->get_iter_from_string($num);
	if ($newtext == "") {
		$store->remove($iter);
		my @newfav = ();
		foreach (0 .. (@favourites - 1)) {
			if ($_ != $num) {
				push @newfav, $favourites[$_];
			}
		}
		@favourites = @newfav;
	} else {
		$store->set($iter, 0, $newtext);
		$favourites[$num] = $newtext;
	}
	return TRUE;
}

sub add_favourite {
	my $store   = $prefsxml->get_widget('treeHosts')->get_model();
	my $newtext = $prefsxml->get_widget('entryAddFav')->get_text;
	$store->set($store->append, 0, $newtext);
	push @favourites, $newtext;
}

sub handle_key {
	my ($widget, $event) = @_;
	use Data::Dumper;
	if ($event->state =~ /control-mask/) {
		if ($event->keyval == 122) {
			finish_text();
			undo_last();
			return TRUE;
		} elsif ($event->keyval == 121) {
			finish_text();
			redo_last();
			return TRUE;
		}
	}
	return FALSE;
}

sub add_image {
	my ($widget) = @_;
	if (toggle_tool("buttonPicture")) {
		my $xml = Gtk2::GladeXML->new($gladefile, 'dialogFile');
		$xml->signal_autoconnect_from_package('');
		my $response = $xml->get_widget('dialogFile')->run();
		if ($response eq "ok") {
			my $id = time;
			if (int($lastid) == $id) {
				$id = $lastid + 0.1;
			}
			$lastid = $id;

			my $pagenum =
			  $mainxml->get_widget('notebookMain')->get_current_page;
			my $canvas =
			  $mainxml->get_widget('notebookMain')->get_nth_page($pagenum)
			  ->get_child;
			my $filename    = $xml->get_widget('dialogFile')->get_filename();
			my $newfilename = $canvas->{user_data} . "@" . $id;
			system(
				"cp \"" . $filename . "\" \"" . $svgdir . $newfilename . "\"");
			my $im = Gtk2::Gdk::Pixbuf->new_from_file($svgdir . $newfilename);
			$canvasImage =
			  Gnome2::Canvas::Item->new($canvas->root, 'Gnome2::Canvas::Pixbuf',
				"pixbuf", $im, "x", 0, "y", 0, "width", $im->get_width,
				"height", $im->get_height, "anchor", 'nw',);
			$canvasImage->{user_data} = $newfilename;
			$brush_type = IMAGE;
		}
		$xml->get_widget('dialogFile')->destroy();
	}
}

sub toggle_thick {
	my ($button) = @_;
	if ($mainxml->get_widget($button)->get_active()) {
		foreach
		  my $item ('buttonFine', 'buttonMedium', 'buttonThick', 'buttonHeavy')
		{
			if ($item ne $button) {
				if ($mainxml->get_widget($item)->get_active()) {
					$mainxml->get_widget($item)->set_active(FALSE);
				}
			}
		}
	}
	return $mainxml->get_widget($button)->get_active();
}

sub toggle_tool {
	my ($button) = @_;
	print $button. "\n";
	if ($mainxml->get_widget($button)->get_active()) {
		foreach my $item (
			'buttonBlack',    'buttonWhiteout',
			'buttonHighlite', 'buttonText',
			'buttonTime',     'buttonPicture',
			'buttonDelete'
		  )
		{
			if ($item ne $button) {
				if ($mainxml->get_widget($item)->get_active()) {
					$mainxml->get_widget($item)->set_active(FALSE);
				}
			}
		}
	}
	return $mainxml->get_widget($button)->get_active();
}

sub load_bg {
	my ($widget) = @_;
	my $xml = Gtk2::GladeXML->new($gladefile, 'dialogFile');
	$xml->signal_autoconnect_from_package('');
	my $response = $xml->get_widget('dialogFile')->run();
	if ($response eq "ok") {
		add_page();
		my $id = time;
		if (int($lastid) == $id) {
			$id = $lastid + 0.1;
		}
		$lastid = $id;

		my $pagenum = $mainxml->get_widget('notebookMain')->get_current_page;
		my $canvas = $mainxml->get_widget('notebookMain')->get_nth_page($pagenum)->get_child;
		my $filename    = $xml->get_widget('dialogFile')->get_filename();
		$xml->get_widget('dialogFile')->destroy();
		my $newfilename = $canvas->{user_data} . "@" . $id.".png";
		system( "convert -resize ".($width-100)."x".($height-100)." \"" . $filename . "\" \"" . $svgdir . $newfilename . "\"");
		my $im = Gtk2::Gdk::Pixbuf->new_from_file($svgdir . $newfilename);
		$canvasitem{$canvas->{user_data}}->{$id} = Gnome2::Canvas::Item->new(
			$canvas->root,
			'Gnome2::Canvas::Pixbuf',
			"pixbuf", $im,
			"x", 0,
			"y", 0,
			"width", $im->get_width,
			"height", $im->get_height,
			"anchor", 'nw',);
		my $tag      = $svg{$canvas->{userdata}}->getElement('svg')->createChild('image');
		$tag->attribute('x',          0);
		$tag->attribute('y',          0);
		$tag->attribute('width',      $im->get_width);
		$tag->attribute('height',     $im->get_height);
		$tag->attribute('name',       $id);
		$tag->attribute('xlink:href', $newfilename);
	}
}
